共计 2875 个字符,预计需要花费 8 分钟才能阅读完成。
一个比喻
对于 ThreadLocal 有一个形象的比喻:健身房里的公共贮存柜,这里的贮存柜相当于 threadLocalMap,柜子的钥匙好比 threadLocal 援用,去健身的人好比是一个个 thread,每个健身的人都有一个调配给本人的钥匙,通过这个钥匙能够找到对应的贮存柜而后寄存本人的私人物品。当一个人应用完柜子后这个柜子能够调配给起初的人应用。
threadLocal,threadLocalMap,entry[],thread 的关系
threadLocal 类里有个外部类 threadLocalMap, 而 entry[]是 threadLoacalMap 里的外部类,它继承了 WeakReference, 本身有个 Object 类型的 value 属性。thread 类有个 threadLocal.threadLocalMap 属性。
- 应用时须要应用 static final 润饰吗?
先说 fianl,应用 final 润饰其实是个‘一般’问题,这个与 threadLocal 并没有很间接的关系:对于根本类型不让再批改;对于援用类型不能再指向其它对象。应用 static 润饰的变量是类变量,不论创立多少对象始终都是一个对象,threaLocal 是应用空间换工夫的一种思维,应用 static 润饰后是为了缩小对象的创立,缩小空间占用。当然怎么应用还是须要联合业务场景。 - set 办法
public void set(T value) {
// 获取以后线程
Thread t = Thread.currentThread();
// 取出下面线程里的 threadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 如果 threaLocal 是 static final 润饰则这里的 this 会是同一个 threadLocal
map.set(this, value);
else
createMap(t, value);
}
private void set(ThreadLocal<?> key, Object value) {Entry[] tab = table;
int len = tab.length;
// 获取 Entry 数组下标
int i = key.threadLocalHashCode & (len-1);
// 循环里先取出对应下标的 entry 对象,接着判断是对象是否为空。//for 里的语句 3 是在寻找下一个 entry,也就是产生了碰撞后采纳了凋谢定
// 址法法的线性探测(HashMap 采纳的是链表法)。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 曾经没有线程,或者被执行了 remove 办法,// 再应用则间接应用并做一些清理工作
if (k == null) {replaceStaleEntry(key, value, i);
return;
}
}
// 对应下标值为空,阐明没有抵触,间接将值放入 entry 数组
tab[i] = new Entry(key, value);
int sz = ++size;
// 未能进行清理且 Entry 的数量曾经达到了列表的扩容阈值的 2 / 3 则进行扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();}
- 其它
- 呈现 hash 抵触是能过凋谢定址法里的线性探测解决。
- 在进行 get 查问时如果是呈现了抵触的 key,会持续向后遍历并比拟 key 来获取对应的 value。
- 扩容的过程中也会进行清理 Entry 的逻辑,并将新的 entry 数组长度扩充到旧数组的 2 倍,同时计算新的 hash 值后将旧的数据移到新的 entry 里。
- 在 get,set,rehash,remove 办法里如果条件满足也会进行清理工作。
内存透露问题
在线程池场景下线程应用完并不会销毁而是偿还到池里,如果应用 ThreadLocal 会导致线程始终持有 threadlocal,也就是 thread->threadlocal->threadlocalmap->entry<key,value> 这样一个援用链路,只有当 remove 办法被调用后,key 会被置为 null,而 key 是一个 WeakReference, 这样在 GC 的时候这个 entry 才会被回收。
说说 InheritableThreadLocal
主线程派生出子线程后,在主线程里放入 threadLocal 的值在子线程里是取不到的,而放在 InheritableThreadLocal 里的值就能够在子线程里取出来,创立 Thread 对象时,会判断父线程中 inheritableThreadLocals 是否不为空,如果不为空,则会将父线程中 inheritableThreadLocals 中的数据复制到本人的 inheritableThreadLocals 中。这样就实现了父线程和子线程的上下文传递:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
......
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
......
}
这样父子线程间传值的问题就解决了,但这种形式在线程池里行不通, 能够如下操作:在创立 Task 的时候应用一个变量将业务线程里的 inheritThreadLocal 设置的值保留下来,而后在线程池里的线程执行过程中用上一步存下来的值赋给以后线程,示例能够参考:线程池 InheritableThreadLocal 问题,另外阿里的 TransmittableThreadLocal 已做了很好的封装,在应用时须要留神一点:
在应用 TransmittableThreadLocal 的同时,须要应用 TtlExecutors 对线程池进行封装,阐明:ThreadLocal 系列(三)-TransmittableThreadLocal 的应用及原理解析
最初说说 FastThreadLocal
能够参考:原来这就是比 ThreadLocal 更快的玩意
- 不再应用 hash 算法。
- 为每个 FastThreadLocal 生成惟一一个 index,防止 ThreadLocal 里的 hash 抵触。
- get,set 办法里会注册后台任务应用后盾线程进行清理工作,而 ThreadLocal 里是同步清理,这会对 get,set 性能有影响。(不过两者都倡议手动进行清理)
- 扩容时不再须要 rehash。
- FastThreadLocal 应用字节填充解决伪共享。