先说结论:我们new出来的theadlocal对象存放了值之后,虽然里面的key是弱引用,但是这个外面new出来的threadlocal对象是强引用,此时不会将这个key回收
-
ThreadLocal
是用自己作为key
,不是线程作为key
,线程类有一个类型为ThreadLocalMap
的属性,每个线程包含了独有的ThreadLocalMap
然后用ThreadLocal
为key
去获取value
,线程之间能隔离获取就是因为ThreadLocalMap
是隔离的,每个线程有独立的ThreadLocalMap
-
-
ThreadLocal
有一个内部类ThreadLocalMap
,ThreadLocalMap
有一个内部类Entry
,这Entry
相当于键值对,里面的键值对是<ThreadLocal k,Object v>
-
-
-
ThreadLocalMap
里面有一个Entry
数组,threadlocal.set(value)
的时候会用当前的ThreadLocal
对象作为key计算哈希值,和数组长度取余得到一个数组下标i,然后构建Entry对象存入i位置-
tab[i] = new Entry(key, value);
-
先不考虑哈希碰撞,下面说
-
-
-
每次用
threadlcoal.get()
的时候先获取当前线程的这个ThreadLocalMap
,然后用当前的ThreadLocal
对象作为key计算在Entry数组中取出Entry对象e,然后e.value
获取值-
由于
Entry
这个键值对中的key
是弱引用,垃圾回收会回收key
,但是Entry
对象还在,value
值也在,key
没了,也就是说此时即使key
被垃圾回收了但是Entry
和value
还在,此时如果能得到这个Entry
对象,就仍然可以得到它的value
-
public class Test { public static void main(String[] args) throws Exception { Thread thread = Thread.currentThread(); //得到线程私有的属性threadLocals,它是ThreadLocal.ThreadLocalMap对象 Field field = thread.getClass().getDeclaredField("threadLocals"); field.setAccessible(true);//由于不是public属性,爆破 //这里不能用ThreadLocal.ThreadLocalMap指向,因为ThreadLocalMap不是public得不到 Object o1 = field.get(thread); //得到ThreadLocalMap的table属性,他是一个Entry数组, Field table = o1.getClass().getDeclaredField("table"); table.setAccessible(true); //得到Entry数组,数组是支持向上转型的 Object[] o2 = (Object[])table.get(o1); ThreadLocal<MyObject> local = new ThreadLocal<>(); local.set(new MyObject()); //得到ThreadLocal计算的哈希值字段,用于计算数组下标 Field field1 = local.getClass().getDeclaredField("threadLocalHashCode"); field1.setAccessible(true); int hashcode = (int)field1.get(local); System.out.println(local.get());//输出 //触发垃圾回收 local = null; System.gc(); Thread.sleep(1000); //根据哈希值计算数组下标,得到Entry,然后获取它的value属性 Object o = o2[hashcode & (o2.length - 1)]; Field field2 = o.getClass().getDeclaredField("value"); field2.setAccessible(true); Object value = field2.get(o); System.out.println(value);//最后输出 } } class MyObject { @Override public String toString() { return "得到该对象"; } }
-
- 由此可见即使
Entry
中的threadlocal用了弱引用,被回收了也还是能得到value
- 由此可见即使
-
注意,虽然
ThreadLocal
对象是弱引用,但是上面触发垃圾回收仍然需要先将local=null
,否则无法回收。因为此时Entry
中的ThreadLocal
虽然是弱引用,但是外面这个传入的ThreadLocal
对象是强引用,而且这两个对象是同一个对象,此时不会回收-
ps:垃圾回收器判断是否回收一个对象,用的是可达性分析法,从GC ROOT对象出发遍历所有被涉及的对象,没被遍历到的视为不可达,就当垃圾进行回收,如果一个对象仅仅有一个弱引用可达,也会被回收
- 此时我们的
ThreadLocal
对象再栈中有强引用指向,因此即使Entry
中是弱引用也不会回收- 证明:将
local=null
注掉,断点分析查看Entry数组中的引用是否还在- 可以看到entry中
referent
字段仍有threadlocal
的引用,不会回收 - 取消注释,发现已经被回收,
referent=null
- 证明:将
- 此时我们的
-
-
-
那么问题来了,既然有没有
ThreadLocal
对象都能获取value
,那为什么还要存储它,只存value
不就行了?-
原因是在调用
ThreadLocal
对象的set()
方法的时候,可能发生哈希碰撞-
此时我们需要判断:这个哈希碰撞是同一个
ThreadLocal
对象产生的还是不同的ThreadLocal
对象得到了同样的哈希值- 如果使用一个
ThreadLocal
对象,就覆盖原先的value
- 如果是不同的,就用开放定址法解决冲突
private void set(ThreadLocal<?> key, Object value) { Entry[] tab = table; int len = tab.length; int i = key.threadLocalHashCode & (len-1); //nextIndex就是返回下一个下标,如果此时是最后一个就返回0,开放定址法 for (Entry e = tab[i];e != null;e = tab[i = nextIndex(i, len)]) { ThreadLocal<?> k = e.get(); //使用一个对象就覆盖 if (k == key) { e.value = value; return; } //entry不为空k为空,表示之前的entry弱引用被回收,另外处理 if (k == null) { replaceStaleEntry(key, value, i); return; } } //找到空位置 tab[i] = new Entry(key, value); int sz = ++size; if (!cleanSomeSlots(i, sz) && sz >= threshold) rehash(); }
- 如果使用一个
-
-