ThreadLocal对象可以提供线程局部变量,每个线程Thread拥有一份自己的副本变量,多个线程互不干扰。
Thread类有一个类型为ThreadLocal.ThreadLocalMap的实例变量threadLocals,也就是说每个线程有一个自己的ThreadLocalMap。
ThreadLocalMap有自己的独立实现,可以简单地将它的key视作ThreadLocal,value为代码中放入的值(实际上key并不是ThreadLocal本身,而是它的一个弱引用)。
每个线程在往ThreadLocal里放值的时候,都会往自己的ThreadLocalMap里存,读也是以ThreadLocal作为引用,在自己的map里找对应的key,从而实现了线程隔离。
ThreadLocalMap有点类似HashMap的结构,只是HashMap是由数组+链表实现的,而ThreadLocalMap中并没有链表结构。
我们还要注意Entry, 它的key是ThreadLocal k ,继承自WeakReference, 也就是我们常说的弱引用类型。
复习Java中的四种引用:
看一下利用反射得到ThreadLocal中的情况和GC后的情况
public class ThreadLocalDemo {
public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException, InterruptedException {
Thread t = new Thread(()->test("abc",false));
t.start();
t.join();
System.out.println("--gc后--");
Thread t2 = new Thread(() -> test("def", true));
t2.start();
t2.join();
}
private static void test(String s,boolean isGC) {
try {
new ThreadLocal<>().set(s);
if (isGC) {
System.gc();
}
Thread t = Thread.currentThread();
Class extends Thread> clz = t.getClass();
Field field = clz.getDeclaredField("threadLocals");
field.setAccessible(true);
Object ThreadLocalMap = field.get(t);
Class> tlmClass = ThreadLocalMap.getClass();
Field tableField = tlmClass.getDeclaredField("table");
tableField.setAccessible(true);
Object[] arr = (Object[]) tableField.get(ThreadLocalMap);
for (Object o : arr) {
if (o != null) {
Class> entryClass = o.getClass();
Field valueField = entryClass.getDeclaredField("value");
Field referenceField = entryClass.getSuperclass().getSuperclass().getDeclaredField("referent");
valueField.setAccessible(true);
referenceField.setAccessible(true);
System.out.println(String.format("弱引用key:%s,值:%s", referenceField.get(o), valueField.get(o)));
}
}
} catch (Exception e) { e.printStackTrace(); }
}
}
输出的结果如下:
这里直接使用 new ThreadLocal<>().set(s);生成ThreadLocal,key只有个弱引用,GC后被回收。
如果这里使用对象引用一下 ThreadLocal threadLocal = new ThreadLocal<>(); threadLocal.set(s);
GC后因为threadLocal指向key存在一个强引用,key并不会被GC回收。
如果不使用强引用的话,GC回收掉key就只剩value不会被回收永远存在,出现内存泄漏。
既然ThreadLocal存在一个key-value的Map结构,那ThreadLocalMap也会实现自己的hash算法来解决散列表数组冲突的问题。
int i = key.threadLocalHashCode & (len - 1);
ThreadLocalMap中的hash方法很简单就是key的hashCode和数组的长度 - 1做与操作,这里 i 就是对应数组的下标位置。
而关键的是threadLocalHashCode的计算,这里有使用一个属性叫做HASH_INCREMENT = 0x61c88647
每当创建一个ThreadLocal对象,这个ThreadLocal.nextHashCode值就增加0x61c88647。
而这个值非常特殊,它就是斐波那契数也就是黄金分割数。hash的增量是这个数字带来的好处就是hash会分布的非常均匀。
即便是用黄金分割数作为hash计算因子,大大的减少了冲突的概率,但是仍然会可能存在冲突。
HashMap中解决冲突是使用了数组结构上又构建了一个链表,拉链式的解决的。
ThreadLocalMap并没有使用这种,它是使用一种线性的解决办法。
查询到 i 为如果当前位置的数据是有效的,比较key是否相同,相同就替换,不相同检查 i + 1位置是否为空不断的向后查找。
在这个过程中,如果遇到了key被回收的桶,也会执行一个replaceStaleEntry()方法,该方法含义是替换过期数据的逻辑,以index=7位起点开始遍历,进行探测式数据清理工作。