Thread Local
ThreadLocal 作用
ThreadLocal 特点
- ThreadLocal 的实现是这样的:每个 Thread 维护一个
ThreadLocalMap
映射表,这个映射表的 key 是ThreadLocal
实例本身,value 是真正需要存储的 Object。 - 也就是说
ThreadLocal
本身并不存储值,它只是作为一个 key 来让线程从ThreadLocalMap
获取 value。值得注意的是图中的虚线,表示ThreadLocalMap
是使用ThreadLocal
的弱引用作为 Key 的,弱引用的对象在 GC 时会被回收。 - ThreadLocalMap 使用 ThreadLocal 的弱引用作为 key,如果一个 ThreadLocal 没有外部强引用来引用它,那么系统 GC 的时候,这个 ThreadLocal 势必会被回收,这样一来,ThreadLocalMap 中就会出现 key 为 null 的 Entry,就没有办法访问这些 key 为 null 的 Entry 的 value,如果当前线程再迟迟不结束的话,这些 key 为 null 的 Entry 的 value 就会一直存在一条强引用链:
Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
永远无法回收,造成内存泄漏。 - 总的来说就是,ThreadLocal 里面使用了一个存在弱引用的 map, map 的类型是
ThreadLocal.ThreadLocalMap.
Map 中的 key 为一个 threadlocal 实例。这个 Map 的确使用了弱引用,不过弱引用只是针对 key。每个 key 都弱引用指向 threadlocal。 当把 threadlocal 实例置为 null 以后,没有任何强引用指向 threadlocal 实例,所以 threadlocal 将会被 gc 回收。但是,我们的 value 却不能回收,而这块 value 永远不会被访问到了,所以存在着内存泄露。因为存在一条从current thread
连接过来的强引用。只有当前 thread 结束以后,current thread
就不会存在栈中,强引用断开,Current Thread、Map value 将全部被 GC 回收。最好的做法是将调用 threadlocal 的 remove 方法,这也是等会后边要说的。 - 其实,ThreadLocalMap 的设计中已经考虑到这种情况,也加上了一些防护措施:在 ThreadLocal 的
get(),set(),remove()
的时候都会清除线程 ThreadLocalMap 里所有 key 为 null 的 value。
内存泄漏的根源
ThreadLocalMap 的生命周期跟 Thread 一样长**,其中的 value 没有及时 remove**
可能引起内存泄漏的情况
(1)使用 static 的 ThreadLocal,延长了 ThreadLocal 的生命周期,可能导致内存泄漏。
(2)分配使用了 ThreadLocal 又不再调用 get(),set(),remove()方法,那么就会导致内存泄漏,因为这块内存一直存在。
为什么使用弱引用
(1)key 使用强引用:引用的ThreadLocal
的对象被回收了,但是ThreadLocalMap
还持有ThreadLocal
的强引用,如果没有手动删除,ThreadLocal
不会被回收,导致 Entry 内存泄漏。
(2)key 使用弱引用:引用的 ThreadLocal 的对象被回收了,由于ThreadLocalMap
持有ThreadLocal
的弱引用,即使没有手动删除,ThreadLocal
也会被回收。value
在下一次ThreadLocalMap
调用set、get、remove
的时候会被清除。
使用弱引用可以多一层保障:弱引用ThreadLocal
不会内存泄漏,对应的 value 在下一次ThreadLocalMap
调用set、get、remove
的时候会被清除
Simple is Awesome