ThreadLocal的作用
当多个线程同时读写同一共享变量时存在并发问题,如下图所示:
如果不共享上图红色的资源,不就没有并发问题了。
也就是说:一个线程存一个自己的变量,类比原来好几个人玩同一个球,现在一个人一个球,那就没有并发问题了。
ThreadLocal原理
如何把变量存在线程上呢?如何把线程的数据隔离呢?下面我们继续详解ThreadLocal的原理。
因为一个线程内,可以存在多个 ThreadLocal 对象,所以其实是ThreadLocal内部维护了一个 Map 。
这个 Map 不是直接使用 HashMap ,而是 ThreadLocal 实现的一个叫做 ThreadLocalMap 的静态内部类,用来存变量。
它的大概结构如下图所示:
ThreadLocalMap是一个Map,key是ThreadLocal,value是Object。
映射到源码就是如下所示:

往ThreadLocalMap里面放值

从ThreadLocalMap里面取值

实际上,ThreadLocalMap内部持有一个 Entry[] 类型的数组 table,每个数组成员都是一个键值对,Entry数组是真正承载数据的地方。
Entry继承自WeakReference,每个Entry 的 key 都是一个 ThreadLocal 对象的弱引用,value 是Object 类型,是强引用,ThreadLocalMap 中可以包含多个ThreadLocal对象。
如下图所示:
ThreadLocalMap中包含了多个ThreadLocal对象
那么如果一个线程使用多个ThreadLocal对象,ThreadLocalMap如何区分不同的ThreadLocal呢?
实际上,每一个ThreadLocal对象都包含了一个独一无二的threadLocalHashCode值,使用这个值就可以在KV数组中找到对应的本地变量。
key是ThreadLocal对象的弱引用,之所以使用 WeakReference 类型作为ThreadLocal对象的引用,是出于垃圾回收考虑。
当某个 ThreadLocal对象已经没有强引用指向时,它被 GC 回收,那么它的 ThreadLocalMap 里对应的 Entry 的键值会随之失效。
不过需要注意的是,虽然key值是弱引用,不影响ThreadLocal对象回收,但value值是强引用。
当ThreadLocal被回收,value对象不会被回收,记得要调用 remove() 方法避免内存泄露。