ThreadLocal的理解

遇到了描述ThreadLocal的实现原理和内存泄漏的问题,以前看过ThreadLocal的实现原理,可是网上有不少文章将的很乱,其中有不少文章将ThreadLocal与线程同步机制混为一谈,特别注意的是ThreadLocal与线程同步无关,并非为了解决多线程共享变量问题! 
ThreadLocal官网解释:数组

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its {@code get} or {@code set} method) has its own, independently initialized copy of the variable. {@code ThreadLocal} instances are typically private static fields in classes that wish to associate state with a thread (e.g.,a user ID or Transaction ID)
  • 1

->翻译过来的大概意思就是:ThreadLocal类用来提供线程内部的局部变量。这些变量在多线程环境下访问(经过get或set方法访问)时能保证各个线程里的变量相对独立于其余线程内的变量,ThreadLocal实例一般来讲都是private static类型。 
总结:ThreadLocal不是为了解决多线程访问共享变量,而是为每一个线程建立一个单独的变量副本,提供了保持对象的方法和避免参数传递的复杂性。服务器

ThreadLocal的主要应用场景为按线程多实例(每一个线程对应一个实例)的对象的访问,而且这个对象不少地方都要用到。例如:同一个网站登陆用户,每一个用户服务器会为其开一个线程,每一个线程中建立一个ThreadLocal,里面存用户基本信息等,在不少页面跳转时,会显示用户信息或者获得用户的一些信息等频繁操做,这样多线程之间并无联系并且当前线程也能够及时获取想要的数据。多线程

2、实现原理

ThreadLocal能够看作是一个容器,容器里面存放着属于当前线程的变量。ThreadLocal类提供了四个对外开放的接口方法,这也是用户操做ThreadLocal类的基本方法: 
(1) void set(Object value)设置当前线程的线程局部变量的值。 
(2) public Object get()该方法返回当前线程所对应的线程局部变量。 
(3) public void remove()将当前线程局部变量的值删除,目的是为了减小内存的占用,该方法是JDK 5.0新增的方法。须要指出的是,当线程结束后,对应该线程的局部变量将自动被垃圾回收,因此显式调用该方法清除线程的局部变量并非必须的操做,但它能够加快内存回收的速度。 
(4) protected Object initialValue()返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,在线程第1次调用get()或set(Object)时才执行,而且仅执行1次,ThreadLocal中的缺省实现直接返回一个null。ide

能够经过上述的几个方法实现ThreadLocal中变量的访问,数据设置,初始化以及删除局部变量,那ThreadLocal内部是如何为每个线程维护变量副本的呢?网站

其实在ThreadLocal类中有一个静态内部类ThreadLocalMap(其相似于Map),用键值对的形式存储每个线程的变量副本,ThreadLocalMap中元素的key为当前ThreadLocal对象,而value对应线程的变量副本,每一个线程可能存在多个ThreadLocal。this

源代码:spa

 1 /**
 2  Returns the value in the current thread's copy of this  3  thread-local variable. If the variable has no value for thecurrent thread, it is first initialized to the value returned by an invocation of the {@link #initialValue} method.  4   @return the current thread's value of this thread-local  5  */
 6 public T get() {  7     Thread t = Thread.currentThread();//当前线程
 8     ThreadLocalMap map = getMap(t);//获取当前线程对应的ThreadLocalMap
 9     if (map != null) { 10         ThreadLocalMap.Entry e = map.getEntry(this);//获取对应ThreadLocal的变量值
11         if (e != null) { 12             @SuppressWarnings("unchecked") 13             T result = (T)e.value; 14             return result; 15  } 16  } 17     return setInitialValue();//若当前线程还未建立ThreadLocalMap,则返回调用此方法并在其中调用createMap方法进行建立并返回初始值。
18 } 19 //设置变量的值
20 public void set(T value) { 21    Thread t = Thread.currentThread(); 22    ThreadLocalMap map = getMap(t); 23    if (map != null) 24        map.set(this, value); 25    else
26  createMap(t, value); 27 } 28 private T setInitialValue() { 29    T value = initialValue(); 30    Thread t = Thread.currentThread(); 31    ThreadLocalMap map = getMap(t); 32    if (map != null) 33        map.set(this, value); 34    else
35  createMap(t, value); 36    return value; 37 } 38 /**
39 为当前线程建立一个ThreadLocalMap的threadlocals,并将第一个值存入到当前map中 40 @param t the current thread 41 @param firstValue value for the initial entry of the map 42 */
43 void createMap(Thread t, T firstValue) { 44     t.threadLocals = new ThreadLocalMap(this, firstValue); 45 } 46 //删除当前线程中ThreadLocalMap对应的ThreadLocal
47 public void remove() { 48        ThreadLocalMap m = getMap(Thread.currentThread()); 49        if (m != null) 50            m.remove(this); 51 }

上述是在ThreadLocal类中的几个主要的方法,他们的核心都是对其内部类ThreadLocalMap进行操做,下面看一下该类的源代码:线程

 1 static class ThreadLocalMap {  2   //map中的每一个节点Entry,其键key是ThreadLocal而且仍是弱引用,这也致使了后续会产生内存泄漏问题的缘由。
 3  static class Entry extends WeakReference<ThreadLocal<?>> {  4  Object value;  5            Entry(ThreadLocal<?> k, Object v) {  6                super(k);  7                value = v;  8  }  9     /**
10  * 初始化容量为16,觉得对其扩充也必须是2的指数 11      */
12     private static final int INITIAL_CAPACITY = 16; 13     /**
14  * 真正用于存储线程的每一个ThreadLocal的数组,将ThreadLocal和其对应的值包装为一个Entry。 15      */
16     private Entry[] table; 17 
18 
19     ///....其余的方法和操做都和map的相似
20 }

总之,为不一样线程建立不一样的ThreadLocalMap,用线程自己为区分点,每一个线程之间其实没有任何的联系,说是说存放了变量的副本,其实能够理解为为每一个线程单独new了一个对象。翻译

3、内存泄漏问题

      在上面提到过,每一个thread中都存在一个map, map的类型是ThreadLocal.ThreadLocalMap. Map中的key为一个threadlocal实例. 这个Map的确使用了弱引用,不过弱引用只是针对key. 每一个key都弱引用指向threadlocal. 当把threadlocal实例置为null之后,没有任何强引用指向threadlocal实例,因此threadlocal将会被gc回收. 可是,咱们的value却不能回收,由于存在一条从current thread链接过来的强引用. 只有当前thread结束之后, current thread就不会存在栈中,强引用断开, Current Thread, Map, value将所有被GC回收. 
  因此得出一个结论就是只要这个线程对象被gc回收,就不会出现内存泄露,但在threadLocal设为null和线程结束这段时间不会被回收的,就发生了咱们认为的内存泄露。其实这是一个对概念理解的不一致,也没什么好争论的。最要命的是线程对象不被回收的状况,这就发生了真正意义上的内存泄露。好比使用线程池的时候,线程结束是不会销毁的,会再次使用的。就可能出现内存泄露。设计

相关文章
相关标签/搜索