ThreadLocal原理
- ThreadLocal对象new出来存放到堆中,ThreadLocal引用是存放在栈里
- Thread 类有个 ThreadLocalMap 成员变量,Map的key是Threadlocal 对象,value是你要存放的线程局部变量。
public void set(T value) {//获取当前线程Thread,就是上图画的Thread 引用Thread t = Thread.currentThread(); //Thread类有个成员变量ThreadlocalMap,拿到这个MapThreadLocalMap map = getMap(t);if (map != null)//this指的就是Threadlocal对象map.set(this, value);elsecreateMap(t, value);
}ThreadLocalMap getMap(Thread t) {//获取线程的ThreadLocalMapreturn t.threadLocals;
}void createMap(Thread t, T firstValue) {//初始化t.threadLocals = new ThreadLocalMap(this, firstValue);
}
- ThreadLocalMap 类的定义在 Threadlocal中。结构与HashMap一样
- 使用ThreadLocal后一定手动remove掉 引用ThreadLocal的对象被回收了,由于ThreadLocalMap持有ThreadLocal的弱引用,即使没有手动删除,ThreadLocal也会被回收。value在下一次ThreadLocalMap调用set、get、remove的时候会被清除。
- ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。
既然是线程局部变量,那为什么不用线程对象(Thread对象)作为key,这样不是更清晰,直接用线程作为key获取线程变量?
- 这样设计会有个问题,比如: 我已经把用户信息存在线程变量里了,这个时候需要新增加一个线程变量,比方说新增用户地理位置信息
- 我们ThreadlocalMap 的key用的是线程,再存一个地理位置信息,key都是同一个线程(key一样),会覆盖原来的用户信息。
为什么ThreadLocal中的key设计成WeakReference
ThreadLocal<UserInfo> userInfoThreadLocal = new ThreadLocal<>();
userInfoThreadLocal.set(userInfo);
- 为了尽大可能的避免内存泄漏
- new出的ThreadLocal被userInfoThreadLocal强引用,同时也被ThreadLocalMap的Key弱引用
- gc要回收ThreadLocal对象的前提是它只被WeakReference引用
- 所以只要ThreadLocal对象如果还被 userInfoThreadLocal(强引用) 引用着,GC是不会回收被WeakReference引用的对象的。
- 有些线程会用线程池优化,线程生命周期很长,根据JVM 根搜索算法,一直存在 Thread -> ThreadLocalMap -> Entry(元素)这样一条引用链路,如果key不设计成WeakReference类型,是强引用的话,就一直不会被GC回收,key就一直不会是null,不为null Entry元素就不会被清理(ThreadLocalMap是根据key是否为null来判断是否清理Entry)
- 所以ThreadLocal的设计者认为只要ThreadLocal 所在的作用域结束了工作被清理了,GC回收的时候就会把key引用对象回收,key置为null,ThreadLocal会尽力保证Entry清理掉来最大可能避免内存泄漏。
- ThreadLocalMap中Entry 继承了WeakReference类,Entry 中的 key 是WeakReference类型的,在Java 中当对象只被 WeakReference 引用,没有其他对象引用时,被WeakReference 引用的对象发生GC 时会直接被回收掉。