一文搞懂 ThreadLocal 原理

当多线程访问共享可变数据时,涉及到线程间同步的问题,并不是所有时候,都要用到共享数据,所以就需要线程封闭出场了 。
数据都被封闭在各自的线程之中,就不需要同步,这种通过将数据封闭在线程中而避免使用同步的技术称为线程封闭 。
本文主要介绍线程封闭中的其中一种体现:ThreadLocal,将会介绍什么是 ThreadLocal;从 ThreadLocal 源码角度分析,最后介绍 ThreadLocal 的应用场景 。
什么是 ThreadLocal?ThreadLocal 是 JAVA 里一种特殊变量,它是一个线程级别变量,每个线程都有一个 ThreadLocal 就是每个线程都拥有了自己独立的一个变量,竞态条件被彻底消除了,在并发模式下是绝对安全的变量 。
可以通过 ThreadLocal<T> value = https://www.isolves.com/it/cxkf/bk/2020-07-29/new ThreadLocal(); 来使用 。
会自动在每一个线程上创建一个 T 的副本,副本之间彼此独立,互不影响,可以用 ThreadLocal 存储一些参数,以便在线程中多个方法中使用,用以代替方法传参的做法 。
下面通过例子来了解下 ThreadLocal:
public class ThreadLocalDemo {/*** ThreadLocal变量,每个线程都有一个副本,互不干扰*/public static final ThreadLocal<String> THREAD_LOCAL = new ThreadLocal<>();public static void main(String[] args) throws Exception {new ThreadLocalDemo().threadLocalTest();}public void threadLocalTest() throws Exception {// 主线程设置值THREAD_LOCAL.set("wupx");String v = THREAD_LOCAL.get();System.out.println("Thread-0线程执行之前," + Thread.currentThread().getName() + "线程取到的值:" + v);new Thread(new Runnable() {@Overridepublic void run() {String v = THREAD_LOCAL.get();System.out.println(Thread.currentThread().getName() + "线程取到的值:" + v);// 设置 threadLocalTHREAD_LOCAL.set("huxy");v = THREAD_LOCAL.get();System.out.println("重新设置之后," + Thread.currentThread().getName() + "线程取到的值为:" + v);System.out.println(Thread.currentThread().getName() + "线程执行结束");}}).start();// 等待所有线程执行结束Thread.sleep(3000L);v = THREAD_LOCAL.get();System.out.println("Thread-0线程执行之后," + Thread.currentThread().getName() + "线程取到的值:" + v);}}首先通过 static final 定义了一个 THREAD_LOCAL 变量,其中 static 是为了确保全局只有一个保存 String 对象的 ThreadLocal 实例;final 确保 ThreadLocal 的实例不可更改,防止被意外改变,导致放入的值和取出来的不一致,另外还能防止 ThreadLocal 的内存泄漏 。上面的例子是演示在不同的线程中获取它会得到不同的结果,运行结果如下:
Thread-0线程执行之前,main线程取到的值:wupxThread-0线程取到的值:null重新设置之后Thread-0线程取到的值为:huxyThread-0线程执行结束Thread-0线程执行之后,main线程取到的值:wupx首先在 Thread-0 线程执行之前,先给 THREAD_LOCAL 设置为 wupx,然后可以取到这个值,然后通过创建一个新的线程以后去取这个值,发现新线程取到的为 null,意外着这个变量在不同线程中取到的值是不同的,不同线程之间对于 ThreadLocal 会有对应的副本,接着在线程 Thread-0 中执行对 THREAD_LOCAL 的修改,将值改为 huxy,可以发现线程 Thread-0 获取的值变为了 huxy,主线程依然会读取到属于它的副本数据 wupx,这就是线程的封闭 。
看到这里,我相信大家一定会好奇 ThreadLocal 是如何做到多个线程对同一对象 set 操作,但是 get 获取的值还都是每个线程 set 的值呢,接下来就让我们进入源码解析环节:
ThreadLocal 源码解析首先看下 ThreadLocal 都有哪些重要属性:
// 当前 ThreadLocal 的 hashCode,由 nextHashCode() 计算而来,用于计算当前 ThreadLocal 在 ThreadLocalMap 中的索引位置private final int threadLocalHashCode = nextHashCode();// 哈希魔数,主要与斐波那契散列法以及黄金分割有关private static final int HASH_INCREMENT = 0x61c88647;// 返回计算出的下一个哈希值,其值为 i * HASH_INCREMENT,其中 i 代表调用次数private static int nextHashCode() {return nextHashCode.getAndAdd(HASH_INCREMENT);}// 保证了在一台机器中每个 ThreadLocal 的 threadLocalHashCode 是唯一的private static AtomicInteger nextHashCode = new AtomicInteger();其中的 HASH_INCREMENT 也不是随便取的,它转化为十进制是 1640531527,2654435769 转换成 int 类型就是 -1640531527,2654435769 等于 (√5-1)/2 乘以 2 的 32 次方 。(√5-1)/2 就是黄金分割数,近似为 0.618,也就是说 0x61c88647 理解为一个黄金分割数乘以 2 的 32 次方,它可以保证 nextHashCode 生成的哈希值,均匀的分布在 2 的幂次方上,且小于 2 的 32 次方 。


推荐阅读