参考:https://www.cnblogs.com/xzwblog/p/7227509.html
开局一张图,内容全靠编!!!
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).
大意是指:ThreadLocal类提供线程局部变量,这种变量作用于线程的生命周期内。作用:提供一个线程内公共变量,减少同一个线程内多个函数或者组件之间一些公共变量的传递的复杂度,或者为线程提供一个私有的变量副本,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
下面进行源码分析:
1、先看构造函数:
/**
* Creates a thread local variable.
* @see #withInitial(java.util.function.Supplier)
*/
public ThreadLocal() {
}
2、查看set()方法:
1、首先通过Thread.currentThread();获取当前线程t
2、通过getMap()方法得到Thread类的成员变量threadLocals、它是ThreadLocal.ThreadLocalMap类型
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class.
*/
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap是一个自定义的HashMap,用于维护ThreadLocal的值。ThreadLocalMap使用ThreadLocal的弱引用作为Key,如果一个ThreadLocal没有外部强引用引用他,那么系统GC的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄露。
ThreadLocalMap的设计对策:
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它,防止内存泄露。
总结set()方法:首先拿到当前的线程,然后拿到存储在线程中的ThreadLocalMap对象(自定义的HashMap),如果map != null,把当前的ThreadLocal对象作为key,以及参数value,存储到map中。如果map == null,则创建ThreadLocalMap对象。
创建一个ThreadLocalMap对象,然后把当前的ThreadLocal和firstValue作为键值对存储进去并赋值给当前线程的threadLocalMap。
3、get()方法:
拿到当前的线程的ThreadLocalMap对象。进而通过ThreadLocalMap.Entry拿到对应的value。如果ThreadLocalMap == null,则调用setInitialValue()方法并返回value(null)
在ininitialValue()方法中返回了null,即 value == null;然后又是创建了一个ThreadLocalMap对象,并将<currentThread,null>丢了进去。
总结:
1、Thread类中有个成员变量属于ThreadLocalMap类(一个定义在ThreadLocal类中的内部类),它是一个Map,key是当前的Thread的实例
2、当调用set(T t)时,首先获取当前线程的ThreadLocalMap类属性,然后以当前Thread的实例为key,t 为value,get值类似。
3、ThreadLocal变量的活动范围为某线程,是改线程“专有、独占”的,对该变量的所有操作均由该线程完成!也就是说,ThreadLocal 不是用来解决共享对象的多线程访问的竞争问题的,因为ThreadLocal.set() 到线程中的对象是该线程自己使用的对象,其他线程是不需要访问的,也访问不到的。当线程终止后,这些值会作为垃圾回收。
4、ThreadLocal的工作原理决定了:每个线程独自拥有一个变量,并非是可共享的。
网友评论