结构
每个Thread内部都有一个独立的threadLocals
,其维护着一个table[]数组
把每个ThreadLocal对象的(hashcode&len-1)
作为key找到对应的table[]所在位置,set到线程内部的threadLocals
,这样保持线程之间资源独立
threadLocals内部的table每个对象是一个WeakReference
类型entry
,弱引用在没有强引用指向对象的时候,会在gc的时候回收对象,这样key就会变成null,而value和value指向的对象还是存在强引用关系(ThreadLocal自带了解决方案是每次remove/set/get的时候都会将key==null
的entry删除)
防止线程池串线程问题
- 直接使用ThreadLocal,每次
init()
或者destroy()
时,主动remove()
- 使用InheritableThreadLocal,
防止NullPointException
- 重写 threadLocal的
initialValue()
方法,此方法是一个延迟加载
方法 - 如果不想每次
get()
前set()
,就重写initialValue - 返回此线程局部变量的当前线程的初始值。最多在每次访问线程来获得每个线程局部变量时调用此方法一次,即线程第一次回使用
get()
方法访问变量的时候。如果线程先于 get 方法调用 set(T) 方法,则不会在线程中再调用 initialValue 方法 - 推荐使用 private static来存储ThreadLocal,减少不必要的创建开销
注意:
- 使用ThreadLocal,一般都是声明在静态变量中,如果不断的创建ThreadLocal而且没有调用其remove方法,将会导致内存泄露。因为ThreadLocal使用弱引用,不主动删除,
ThreadLocal其实存在引用,会导致ThreadLocal无法被自动回收 - 如果是static的ThreadLocal,一般不需要调用remove(这里只是延长了生命周期)个人建议用完还是调用remove删除
ThreadLocal 内存泄漏问题(存在但是基本不会碰上)
泄漏案例1
ThreadLocalMap使用ThreadLocal的弱引用作为key,如果一个ThreadLocal没有外部强引用来引用它,那么系统 GC 的时候,这个ThreadLocal势必会被回收,这样一来,ThreadLocalMap中就会出现key为null的Entry,就没有办法访问这些key为null的Entry的value,如果当前线程再迟迟不结束的话,这些key为null的Entry的value就会一直存在一条强引用链:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value永远无法回收,造成内存泄漏。
其实,ThreadLocalMap的设计中已经考虑到这种情况,也加上了一些防护措施:在ThreadLocal的get(),set(),remove()的时候都会清除线程ThreadLocalMap里所有key为null的value。
但是这些被动的预防措施并不能保证不会内存泄漏:
- 使用
static
的ThreadLocal
,延长了ThreadLocal
的生命周期,可能导致的内存泄漏内存泄漏分析并且使用了线程池,而每个线程如果不回收value,就会保存在内存,如果每个线程持有5MB的数据,共200个线程就会占用1GB的内存 - 分配使用了
ThreadLocal
又不再调用get()
,set()
,remove()
方法,那么就会导致内存泄漏