功能
提供本地线程变量。放在threadLocal的内容只能被当前线程看到
实现
简单理解就是每个Thread都有一个ThreadLocalMap变量,这个变量里维护了threadLocal对象和value的映射关系,threadLocal的get/set方法,其实都是操作的当前线程的threadLocalMap变量
ThreadLocalMap本质是一个Entry数组,看下它的定义
1 | static class ThreadLocalMap { |
每个entry维护了一个threadLocal对象和value的映射关系,Entry有两点需要说明:
- entry对象是一个指向threadLocal对象的弱引用,还负责维护value值
- entry数组的下标与threadLocal对象的hashCode值强绑定
第一点从Entry类定义就能知道,第二点看一下threadLocal的set方法就能明白
1 | private void set(ThreadLocal<?> key, Object value) { |
threadlocal的set方法:
构建一个弱引用entry,指向threadLocal对象,value为set的值,丢到当前线程threadLocalMap的entry数组里,下标基于threadLocal的hashCode计算得到
threadlocal的get方法简单总结如下,不贴代码了:
拿到当前线程的threadLocalMap,根据threadLocal的hash值取出entry数组里对应的entry,再拿到value
threadlocal的OOM问题
先说一个结论:如果只有弱引用指向这个对象,在下一次gc时,这个对象会被gc!
当threadLocal对象只被弱引用entry对象引用时,下一次gc就会回收threadlocal对象。当threadLocal被gc后,指向它的弱引用entry对象并不会被gc,因为thread-->threadLocalMap-->entry[]
这个引用链都是强引用。但entry.value永远无法被访问,为什么呢?
前面说过,entry数组的下标基于threadLocal对象的hashCode计算得到,既然threadLocal被gc,那么这个entry对象就无法被访问,entry.value也就无法被访问。直到thread执行结束被gc时,entry对象才会被gc。如果内存中有很多这种entry对象,会存在OOM的风险。
思考:
为什么entry要用弱引用指向threadLocal?
很简单,因为只有entry用弱引用,当threadLocal被gc时,我们才能感知到,然后把entry也gc!否则,entry用强引用,只有线程退出了,entry才会被gc,更容易OOM!用软引用也不行,软引用只有当OOM时,才会将不在其他地方被引用的threadLocal给gc了,这被感知到时已经为时已晚了!
解决办法:
jvm会在每次调用threadLocal.set\get\remove方法时,会把这些entry(弱引用指向的threadlocal被回收)对象gc,这里不去深究,不过好的习惯是在finally块里手动调用threadLocal.remove来主动触发无效entry的gc
InheritableThreadLocal
简单理解它就是一种父子线程可共享的threadLocal
Thread除了threadLocals维护threadLocal,还有一个inheritableThreadLocals,维护可被子线程共享的threadLocal
1 | /* ThreadLocal values pertaining to this thread. This map is maintained |
如果线程使用InheritableThreadLocal,调用InheritableThreadLocal的createMap方法时会维护到thread.inheritableThreadLocals
1 | public class InheritableThreadLocal<T> extends ThreadLocal<T> { |
需要注意的是,只有在子线程创建的时候,构造函数里会去父线程的inheritableThreadLocals拷贝一份到自己的inheritableThreadLocals,之后父线程set的inheritableThreadLocal值不会被子线程获取
1 | if (inheritThreadLocals && parent.inheritableThreadLocals != null) |