ThreadLocal
允许为每个线程创建独立的变量副本,使得同一个ThreadLocal
对象在不同的线程中拥有不同的值。它的主要作用是在并发环境下提供线程隔离,避免多个线程共享同一个变量,从而减少线程间的相互干扰。
ThreadLocal
的核心在于为每个线程维护一个独立的数据副本,它的实现主要依赖于每个线程维护一个ThreadLocalMap
,这是ThreadLocal
专用的Map,用来存储线程自己的变量。
1.1. ThreadLocalMap底层数据结构
ThreadLocalMap
是一个定制化的Map,其结构类似于HashMap,都是以Key-Value的键值对形式进行存储,其中Key存储的是ThreadLocal实例
,Value存储的是对应的对象,默认为Object。相比于HashMap有一些不同之处:
- 弱引用的键:
ThreadLocalMap
的键(即ThreadLocal对象
)使用了弱引用(强引用>软引用>弱应用>虚引用),因此当没有其他地方引用该ThreadLocal
对象时,GC就会回收它。 - 线性探测解决哈希冲突:区别于HashMap中的链地址法解决哈希冲突,
ThreadLocalMap
使用线性探测法来解决哈希冲突,并且负载因子为2/3。 - 潜在内存泄漏:由于
ThreadLocalMap
中的键是弱引用,但其存储的Value是强应用,如果ThreadLocal
对象被GC回收,而没有调用remove()
方法清理值,那么ThreadLocalMap
中的值就有可能会一直存在,导致内存泄漏。因此在不适用ThreadLocal
后,要及时的调用remove()
方法,手动清除线程的副本变量。或者使用try-finally
模式来保证在完成工作后调用remove()
。
1.2. 能否使用ThreadLocal往线程中存储多个副本变量?
默认情况下,ThreadLocal
每个线程只能存储一个值,因为它的设计初衷就是让每个线程独立的维护一组与ThreadLocal
对象相关的值,也就是说,每个ThreadLocal
实例只能存储一个值。
虽然 ThreadLocal
本身每个实例只能存储一个值,但多个 ThreadLocal
实例在同一个线程中是存储在 ThreadLocalMap
里的。因此,当一个线程中存在多个 ThreadLocal
实例时,这些实例及其对应的值就会存储在该线程的 ThreadLocalMap
中。
那如果我们就是想让一个线程拥有多个副本变量该怎么办?
- 法一:使用
ThreadLocal
存储一个容器(如Map或自定义对象)
虽然每个ThreadLocal
实例只能存储一个值,但是其存储的是什么值是由我们决定的,因此可以将想要存储的多个变量放入Map中,以此实现存储多个独立的副本变量。
- 法二:使用多个
ThreadLocal
对象
private static ThreadLocal<String> threadLocal1 = new ThreadLocal<>();
private static ThreadLocal<Integer> threadLocal2 = new ThreadLocal<>();
threadLocal1.set("Thread1");
threadLocal2.set("Thread2");
通过以上代码在每个线程的ThreadLocalMap
中创建了两个ThreadLocal
对象,分别存储"Thread1"和"Thread2",因此可以通过不同的ThreadLocal
实例对象来获取不同的值。