Java并发编程:深入剖析ThreadLocal (总结)

Posted snowwhite

tags:

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

 

 

问答形式总结:

1、 ThreadLocal类的作用

  ThreadLocal的作用是提供线程内的局部变量,这种变量在多线程环境下访问时能够保证各个线程里变量的独立性。

  ThreadLocal用于保存某个线程共享变量:对于同一个static ThreadLocal,不同线程只能从中get,set,remove自己的变量,而不会影响其他线程的变量。

  1、ThreadLocal.get: 获取ThreadLocal中当前线程共享变量的值。

  2、ThreadLocal.set: 设置ThreadLocal中当前线程共享变量的值。

  3、ThreadLocal.remove: 移除ThreadLocal中当前线程共享变量的值。

  4、ThreadLocal.initialValue: ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值。

 

2、 ThreadLocal原理,ThreadLocal是如何实现的?

      首先,在每个线程Thread内部有一个ThreadLocal.ThreadLocalMap类型的成员变量threadLocals,这个threadLocals就是用来存储实际的变量副本的,键值为当前ThreadLocal变量,value为变量副本(即T类型的变量)。

  初始时,在Thread里面,threadLocals为空,当通过ThreadLocal变量调用get()方法或者set()方法,就会对Thread类中的threadLocals进行初始化,并且以当前ThreadLocal变量为键值,以ThreadLocal要保存的副本变量为value,存到threadLocals。

  然后在当前线程里面,如果要使用副本变量,就可以通过get方法在threadLocals里面查找。

       总结一下:

  1)实际的通过ThreadLocal创建的副本是存储在每个线程自己的threadLocals中的;

  2)为何threadLocals的类型ThreadLocalMap的键值为ThreadLocal对象,因为每个线程中可有多个threadLocal变量,就像上面代码中的longLocal和stringLocal;

  3)在进行get之前,必须先set,否则会报空指针异常;

      如果想在get之前不需要调用set就能正常访问的话,必须重写initialValue()方法。

    因为在上面的代码分析过程中,我们发现如果没有先set的话,即在map中查找不到对应的存储,则会通过调用setInitialValue方法返回i,而在setInitialValue方法中,有一个语句是T value = initialValue(), 而默认情况下,initialValue方法返回的是null。

3、 ThreadLocal的使用场景

最常见的ThreadLocal使用场景为 用来解决 数据库连接、Session管理等。

  如:

private static ThreadLocal<Connection> connectionHolder = new ThreadLocal<Connection>() {
    public Connection initialValue() {
        return DriverManager.getConnection(DB_URL);
    }
};
 
    public static Connection getConnection() {
    return connectionHolder.get();
   }

 下面这段代码摘自:

  http://www.iteye.com/topic/103804

private static final ThreadLocal threadSession = new ThreadLocal();
 
public static Session getSession() throws InfrastructureException {
    Session s = (Session) threadSession.get();
    try {
        if (s == null) {
            s = getSessionFactory().openSession();
            threadSession.set(s);
        }
    } catch (HibernateException ex) {
        throw new InfrastructureException(ex);
    }
    return s;
}

 

4、 常说的ThreadLocal内存泄漏问题:

static class Entry extends WeakReference<ThreadLocal<?>> {
 
/** The value associated with this ThreadLocal. */
 
Object value;
 
 
 
Entry(ThreadLocal<?> k, Object v) {
 
   super(k);
 
   value = v;
 
  }
 
}

 

        从上面源码可以看出,ThreadLocalMap使用ThreadLocal的弱引用作为Entry的key,如果一个ThreadLocal没有外部强引用来引用它,下一次系统GC时,这个ThreadLocal必然会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value。

        我们上面介绍的get、set、remove等方法中,都会对key为null的Entry进行清除(expungeStaleEntry方法,将Entry的value清空,等下一次垃圾回收时,这些Entry将会被彻底回收)。

        但是如果当前线程一直在运行,并且一直不执行get、set、remove方法,这些key为null的Entry的value就会一直存在一条强引用练:Thread Ref -> Thread -> ThreadLocalMap -> Entry -> value,导致这些key为null的Entry的value永远无法回收,造成内存泄漏。  

   如何避免内存泄漏?

    为了避免这种情况,我们可以在使用完ThreadLocal后,手动调用remove方法,以避免出现内存泄漏。

 

5、其他总结

总结:

  1. 每个线程都有一个ThreadLocalMap 类型的 threadLocals 属性。
  2. ThreadLocalMap 类相当于一个Map,key 是 ThreadLocal 本身,value 就是我们的值。
  3. 当我们通过 threadLocal.set(new Integer(123)); ,我们就会在这个线程中的 threadLocals 属性中放入一个键值对,key 是 这个 threadLocal.set(new Integer(123))的threadlocal,value 就是值new Integer(123)。
  4. 当我们通过 threadlocal.get() 方法的时候,首先会根据这个线程得到这个线程的 threadLocals 属性,然后由于这个属性放的是键值对,我们就可以根据键 threadlocal 拿到值。 注意,这时候这个键 threadlocal 和 我们 set 方法的时候的那个键 threadlocal 是一样的,所以我们能够拿到相同的值。
  5. ThreadLocalMap 的get/set/remove方法跟HashMap的内部实现都基本一样,通过 "key.threadLocalHashCode & (table.length - 1)" 运算式计算得到我们想要找的索引位置,如果该索引位置的键值对不是我们要找的,则通过nextIndex方法计算下一个索引位置,直到找到目标键值对或者为空。
  6. hash冲突:在HashMap中相同索引位置的元素以链表形式保存在同一个索引位置;而在ThreadLocalMap中,没有使用链表的数据结构,而是将(当前的索引位置+1)对length取模的结果作为相同索引元素的位置:源码中的nextIndex方法,可以表达成如下公式:如果i为当前索引位置,则下一个索引位置 = (i + 1 < len) ? i + 1 : 0。

—————END—————

 

参考文章:

1、 Java并发:ThreadLocal详解 (JoonWhee

2、Java并发编程:深入剖析ThreadLocal (海子)

 

以上是关于Java并发编程:深入剖析ThreadLocal (总结)的主要内容,如果未能解决你的问题,请参考以下文章

Java并发编程:深入剖析ThreadLocal

Java并发编程:深入剖析ThreadLocal

Java并发编程:深入剖析ThreadLocal

Java并发编程:深入剖析ThreadLocal

Java并发编程:深入剖析ThreadLocal

Java并发编程:深入剖析ThreadLocal