ThreadLocal理解

ThreadLocal深入理解

ThreadLocal是线程局部变量,ThreadLocal会为每个使用该变量的线程提供独立的变量副本,这种变量在线程的生命周期内起作用

每个线程有一个自己的ThreadLocalMap,所以每个线程中ThreadLocal的读写都是隔离的,不会相互影响

一个ThreadLocal中只能存储一个Object对象,如果需要存储多个Object对象那么就需要多个ThreadLocal

对于每一个ThreadLocal对象,都有一个final修饰的int型的threadLocalHashcode不可变属性,对于基本数据类型,可以认为它在初始化后就不可以进行修改,所以可以唯一确定一个ThreadLocal对象

作用

  • 保存线程的上下文信息,在任意需要的地方可以获取

  • 每个线程有自己独立的变量副本,且其它线程不可访问,就不存在多线程的安全问题

 

ThreadLocal实现原理

ThreadLocal是一个泛型类,保证可以接受任何类型的对象

因为一个线程内可以存在多个ThreadLocal对象,所以ThreadLocal内部维护了一个ThreadLocalMap的静态内部类,ThreadLocal对象的get、set方法其实都是调用了ThreadLocalMap类对应的get,set方法;

get方法:
public T get() {   
       Thread t = Thread.currentThread();  
       ThreadLocalMap map = getMap(t);  
       if (map != null)  
           return (T)map.get(this);  

       // Maps are constructed lazily. if the map for this thread  
       // doesn't exist, create it, with this ThreadLocal and its  
       // initial value as its only entry.  
       T value = initialValue();  
       createMap(t, value);  
       return value;  
}
set方法:
public void set(T value) {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null)
           map.set(this, value);
       else
           createMap(t, value);
}

createMap方法:

void createMap(Thread t, T firstValue) {   
       t.threadLocals = new ThreadLocalMap(this, firstValue);  
}

内存泄漏问题

static class Entry extends WeakReference<ThreadLocal<?>> {
   /** The value associated with this ThreadLocal. */
   Object value;

   Entry(ThreadLocal<?> k, Object v) {
       super(k);
       value = v;
  }
}

 

ThreadLocalMap中使用的key为ThreadLocal的弱引用,弱引用的特点是:如果这个对象只存在弱引用,那么在下一次垃圾回收的时候会被清理掉;

这样就会导致ThreadLocalMap中使用这个ThreadLocal的key会被清理掉,但是value是强引用,不会被清理,此时就会出现key为null的value;

ThreadLocalMap实现中已经考虑这个问题,在调用get(),set(),remove()的时候会清理掉key为null的记录,如果说存在内存泄漏,那只有在出现了key为null的记录之后,没有手动调用remove方法,并且之后也没有调用get(),set(),remove()方法;

 

Entry中的key值ThreadLocal为什么不设置成强引用?

如果使用强引用,当原来的key对象失效之后,jvm不会回收map里面的threadLocal

一个线程对应一块工作空间,线程可以存储多个ThreadLocal,那么假设开启一万个线程,每个线程创建一万个ThreadLocal,也就是每个线程维护一万个ThreadLocal的内存空间,而当线程执行结束以后,如果这些ThreadLocal里的Entry还不会被回收,那么将很容易导致堆内存溢出

 

为什么使用ThreadLocal作为key?

同一个线程中,ThreadLocalMap中的key都相同,所以ThreadLocalMap中存储的数量由ThreadLocal决定,Map中Entry的数量会变少,当Thread销毁后,ThreadLocalMap也会随之消失,减少内存的使用

 

ThreadLocalMap的getEntry函数的流程大概为

  1. 首先从ThreadLocal的直接索引位置(通过ThreadLocal.threadLocalHashCode & (table.length-1)运算得到)获取Entry e,如果e不为null并且key相同则返回e;

  2. 如果e为null或者key不一致则向下一个位置查询,如果下一个位置的key和当前需要查询的key相等,则返回对应的Entry。否则,如果key值为null,则擦除该位置的Entry,并继续向下一个位置查询。在这个过程中遇到的key为null的Entry都会被擦除,那么Entry内的value也就没有强引用链,自然会被回收。仔细研究代码可以发现,set操作也有类似的思想,将key为null的这些Entry都删除,防止内存泄露。   但是光这样还是不够的,上面的设计思路依赖一个前提条件:要调用ThreadLocalMap的getEntry函数或者set函数。这当然是不可能任何情况都成立的,所以很多情况下需要使用者手动调用ThreadLocal的remove函数,手动删除不再需要的ThreadLocal,防止内存泄露。所以JDK建议将ThreadLocal变量定义成private static的,这样的话ThreadLocal的生命周期就更长,由于一直存在ThreadLocal的强引用,所以ThreadLocal也就不会被回收,也就能保证任何时候都能根据ThreadLocal的弱引用访问到Entry的value值,然后remove它,防止内存泄露。

即:

   1.使用ThreadLocal,建议用static修饰 static ThreadLocal headerLocal = new ThreadLocal();

  2.使用完ThreadLocal后,执行remove操作,避免出现内存泄漏情况

posted @ 2020-05-17 10:42  zealoterboy  阅读(241)  评论(0编辑  收藏  举报