Java并发编程:线程封闭--ThreadLocal

Posted 小小虫飞飞

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java并发编程:线程封闭--ThreadLocal相关的知识,希望对你有一定的参考价值。

一:线程封闭

  线程封闭:当访问共享的可变数据时,通常需要同步。一种避免同步的方式就是不共享数据。如果仅在单线程内访问数据,就不需要同步,这种技术称为线程封闭

  线程封闭技术一个常见的应用就是JDBC的Connection对象,JDBC规范并没有要求Connection对象必须是线程安全的,在服务器应用程序中,线程从连接池获取一 个Connection对象,使用完之后将对象返还给连接池。下面介绍几种线程封闭技术:

  1: Ad-hoc线程封闭

  2: 栈封闭

  栈封闭是线程封闭的一种特列,在栈封闭中,只能通过局部变量才能访问对象。局部变量的固有属性之一就是封闭在执行栈中,其他线程无法访问这个栈,栈封闭也称为线程内部使用或者线程局部使用。简单的说就是局部变量。多个线程访问一个方法,此方法中的局部变量都会被拷贝一分儿到线程栈中。所以局部变量是不被多个线程所共享的,也就不会出现并发问题。所以能用局部变量就别用全局的变量,全局变量容易引起并发问题。

  3: 维持线程封闭性的一种更加规范方法是使用 ThreadLocal 类,这个类能使线程中某个值与保存值的对象关联起来。ThreadLocal类提供了get和set等访问接口或者方法,这些方法为每个使用该变量的线程都存在一份独立的副本,因此get总是放回当前执行线程在调用set设置的最新值。

  我们通常用ThreadLocal保证可变的单例变量和全局变量不被多线程共享。如果我们的很多操作频繁地用到某个对象,而我们又需要考虑它的线程封闭又需要考虑它的初始化开销,ThreadLocal几乎是最好的选择。

二:ThreadLocal

  首先,ThreadLocal 不是用来解决共享对象的多线程访问问题的,一般情况下,通过ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。各个线程中访问的是不同的对象。 

  另外,说ThreadLocal使得各线程能够保持各自独立的一个对象,并不是通过ThreadLocal.set()来实现的,而是通过每个线程中的new 对象 的操作来创建的对象,每个线程创建一个,不是什么对象的拷贝或副本。通过ThreadLocal.set()将这个新创建的对象的引用保存到各线程的自己的一个map中,每个线程都有这样一个map,执行ThreadLocal.get()时,各线程从自己的map中取出放进去的对象,因此取出来的是各自自己线程中的对象,ThreadLocal实例是作为map的key来使用的。

  其次,看下ThreadLocal的两种经典应用,分别用于Session管理和数据库连接的创建。

 1     private static final ThreadLocal threadSession = new ThreadLocal();
 2 
 3     public static Session getSession() throws InfrastructureException {
 4         Session s = (Session) threadSession.get();
 5         try {
 6             if (s == null) {
 7                 s = getSessionFactory().openSession();
 8                 threadSession.set(s);
 9             }
10         } catch (HibernateException ex) {
11             throw new InfrastructureException(ex);
12         }
13         return s;
14     }

  可以看到在getSession()方法中,首先判断当前线程中有没有放进去session,如果还没有,那么通过sessionFactory().openSession()来创建一个session,再将session set到线程中,实际是放到当前线程的ThreadLocalMap这个map中,这时,对于这个session的唯一引用就是当前线程中的那个ThreadLocalMap(下面会讲到),而threadSession作为这个值的key,要取得这个session可以通过threadSession.get()来得到,里面执行的操作实际是先取得当前线程中的ThreadLocalMap,然后将threadSession作为key将对应的值取出。这个session相当于线程的私有变量,而不是public的。 
显然,其他线程中是取不到这个session的,他们也只能取到自己的ThreadLocalMap中的东西。要是session是多个线程共享使用的,那还不乱套了。 
试想如果不用ThreadLocal怎么来实现呢?可能就要在action中创建session,然后把session一个个传到service和dao中,这可够麻烦的。或者可以自己定义一个静态的map,将当前thread作为key,创建的session作为值,put到map中,应该也行,这也是一般人的想法,但事实上,ThreadLocal的实现刚好相反,它是在每个线程中有一个map,而将ThreadLocal实例作为key,这样每个map中的项数很少,而且当线程销毁时相应的东西也一起销毁了,不知道除了这些还有什么其他的好处。

 1 private static ThreadLocal<Connection> connectionHolder
 2 = new ThreadLocal<Connection>() {
 3 public Connection initialValue() {
 4     return DriverManager.getConnection(DB_URL);
 5 }
 6 };
 7  
 8 public static Connection getConnection() {
 9 return connectionHolder.get();
10 }

 

三:源码解析

  具体的源码解析可见以下博客内容,写的很全面:

  http://www.cnblogs.com/dolphin0520/p/3920407.html

以上是关于Java并发编程:线程封闭--ThreadLocal的主要内容,如果未能解决你的问题,请参考以下文章

《java并发编程实战》读书笔记2--对象的共享,可见性,安全发布,线程封闭,不变性

并发编程-线程安全策略之线程封闭

《java并发编程实战》读书笔记3--对象的组合

Java并发编程与高并发解决方案

Java并发编程安全发布

《Java并发编程实战》第八章 线程池的使用 读书笔记