1.7版本和1.8版本是不一样的。
1.7版本:
ThreadLocal里面维护一个TheradLocalMap,底层是Map,再底层是entry对象,(key,value)形式,key是Thread,value是需要隔离的变量。
1.8版本 :
每个Thread都维护了一个ThreadLocalMap,底层是Map,再底层是entry对象,entry中存储ThreadLocal是key,value是需要隔离的变量。
1.8版本的好处 :
1 .每个Map存储的Entry数量很少,因为1.7版本,每个线程就会代表一个entry,线程越多,entry越多,遍历耗时。1.8版本改成了entry中存储ThreadLocal和value,一般开发过程中,ThreadLocal数量是小于Thread的,所以时间上有优势。因为key少了,也可以避免哈希冲突。hashMap的哈希冲突是拉链法,而ThreadLocalMap的hash冲突是线性探索法。
2 .当Thread销毁的时候,ThreadLocalMap也会随之销毁,减少内存的使用。因为ThreadLocalMap是在Thread里面的,所以只要Thread消失了,那map就不复存在了。
ThreadLocal的set方法原理 :
public void set(T value) { //获取当前线程对象 Thread t = Thread.currentThread(); //获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) //map存在则调用set方法,set设置此实体entry map.set(this, value); else // 1.当前Thread线程不存在ThreadLocalMap对象 // 2. 则调用createMap进行TheradLcoalMap对象的初始化 // 3. 并将t(当前线程)和value(t对应的值)作为第一个entry存放到TheradLocalMap中 createMap(t, value); }
void createMap(Thread t, T firstValue) { //这里的this是调用此方法的ThreadLocal t.threadLocals = new ThreadLocalMap(this, firstValue); }
ThreadLocal的get原理:
public T get() { //获取当前线程对象 Thread t = Thread.currentThread(); //获取此线程对象中维护的ThreadLocalMap对象 ThreadLocalMap map = getMap(t); if (map != null) { //如果此map存在, ThreadLocalMap.Entry e = map.getEntry(this); //以当前的ThreadLocal为key,调用getEntry获取对应的存储实体e if (e != null) { @SuppressWarnings("unchecked") //获取存储实体e对应的value值 //就是我们想要的当前线程对应此ThreadLocal的值。 T result = (T)e.value; return result; } } //初始化: 第一种情况: map不存在,表示此线程没有维护的TheradLocalMap对象 第二种情况: map存在,但是没有与当前ThreadLocal关联的entry。 return setInitialValue(); }
private T setInitialValue() { //调用initialvalue方法获取初始化的值 //则会个方法可以被重写,如果不重写默认返回null; T value = initialValue(); //获取当前线程对象 Thread t = Thread.currentThread(); //获取此线程对象中维护的ThreadLocalMap对象 ThreadLocal.ThreadLocalMap map = getMap(t); if (map != null) //存在则调用map.set设置此实体entry map.set(this, value); else // 1。当前线程Thread不存在ThreadLocalMap对象 // 2。则调用creatMap进行TheradLocalMap对象的初始化 // 3. 并将t(当前线程)和value(t对应的值)作为第一个entry存放至ThreadLocalMap中 createMap(t, value); //返回设置的值 return value; }
总结 :先获取当前线程的ThreadLocalMap变量,如果存在则返回值,不存在则创建并返回初始值。
ThreadLocalMap的基本结构:
ThreadLocalMap的ThreadLocal的内部类,没有实现map接口,用独立的方式实现了Map的功能,其内部的Entry也是独立实现的。
成员变量:
//初始容量,必须是2的整次幂 private static final int INITIAL_CAPACITY = 16; //存放数据的Table,长度也必须是2的整次幂 private ThreadLocal.ThreadLocalMap.Entry[] table; //数组里面entrys的个数,可以用于判断table当前使用量是否超过阈值 private int size = 0; //进行扩容的阈值,表使用量大于它的时候进行扩容。 private int threshold; // Default to 0
存储结构 -Entry
如果key为null(entry.get()==null)意味着key不再被引用,此时entry就可以从table中清除
static class Entry extends WeakReference<ThreadLocal<?>> { /** The value associated with this ThreadLocal. */ Object value; Entry(ThreadLocal<?> k, Object v) { super(k); value = v; } }
在ThreadLocalMap中,用Entry保存key-v结构。不过Entry中的key,只能是ThreadLocal对象,写死的。而且Entry继承自WeakReference,也就是说key是弱引用,只能活到下次GC开始,目的是将ThreadLocal对象的生命周期和线程生命周期进行解绑。
内存泄露问题:
假设是key是强引用,那么当ThreadLocal Ref断开后,依然会有Entry中的key进行引用,只要当前CurrentThreadRef依然存在,那么ThreadLocal就存在强引用,无法被释放。
图下为弱引用,value无法被回收,会导致内存泄漏。
内存泄漏真正的原因:
1.没有手动删除Entry
2.CurrentThread依然在运行。
解决 :
1.ThreadLocal被调用完之后调用其remove方法,把Entry删了,就可以避免内存泄漏。
2.当前线程运行结束后,就结束掉,而不是继续阻塞或者等待运行其他。因为ThreadLocalMThreadap是Thread的一个属性,所以它的生命周期和线程一样长。
扩展问题:
key为什么要用弱引用?
假设用完了没有调用remove方法,也就是说此时key为null,但是value有值,并且无法访问。那么在ThreadLocalMap的set/getEntry方法中,会对key为null,进行判空,如果为null,会把value也设置为null。也就是说,在下一次ThreadLocalMap调用的时候,会把上一次遗留的value清除掉。