1. 介绍
1.1 作用
ThreadLocal的作用主要是做数据隔离,填充的数据只属于当前线程,变量的数据对别的线程而言是相对隔离的。
1.2 原理
每个线程创建时会拥有一个Thread对象,该线程首次调用ThreaLocal.set()方法时,会创建ThreadLocalMap(这不同于HashMap,该Map由一个数组组成),利用线性检测(根据初始key
的hashcode值确定元素在table
数组中的位置,如果发现这个位置上已经有其他key
值的元素被占用,则利用固定的算法寻找一定步长的下个位置,依次判断,直至找到能够存放的位置)解决Hash冲突问题。table数组中每个元素由entry组成,key为弱引用。
1.3 问题
1.3.1 内存泄漏
(1)什么是内存泄漏
在Java中,内存泄漏就是存在一些被分配的对象,这些对象有下面两个特点,首先,这些对象是可达的,即在有向图中,存在通路可以与其相连;其次,这些对象是无用的,即程序以后不会再使用这些对象。如果对象满足这两个条件,这些对象就可以判定为Java中的内存泄漏,这些对象不会被GC所回收,然而它却占用内存。
(2)为什么会内存泄露
线程的生命周期很长,当ThreadLocal
没有被外部强引用的时候就会被GC
回收(给ThreadLocal
置空了):ThreadLocalMap
会出现一个key
为null
的Entry
,但这个Entry
的value
将永远没办法被访问到(后续在也无法操作set、get
等方法了)。如果当这个线程一直没有结束,那这个key
为null
的Entry
因为也存在强引用(Entry.value
),而Entry
被当前线程的ThreadLocalMap
强引用(Entry[] table
),导致这个Entry.value
永远无法被GC
,造成内存泄漏。
(3)如何解决
及时调用ThreadLocal.remove()。
1.3.2 父子之间线程的变量传递丢失
InheritableThreadLocal
提供了一种父子线程之间的数据共享机制。可以解决这个问题。但是InheritableThreadLocal
和线程池使用的时候就会存在问题,因为子线程只有在线程对象创建的时候才会把父线程inheritableThreadLocals
中的数据复制到自己的inheritableThreadLocals
中。这样就实现了父线程和子线程的上下文传递。但是线程池的话,线程会复用,所以会存在问题。可以参考下阿里巴巴的transmittable-thread-local
。