自jdk1.2 版本起 java提供了ThreadLocal,ThreadLocal为每一个使用该变量的线程提供了独立的副本,可以做到线程间的数据隔离,每个线程都可以访问各自内部的副本变量
ThreadLocal使用场景JDK原文翻译:⚠️
此类提供线程局部变量。 这些变量不同于它们的普通对应变量,因为每个访问一个(通过其get或set方法)的线程都有自己的、独立初始化的变量副本。 ThreadLocal实例通常是希望将状态与线程相关联的类中的私有静态字段**(例如,用户 ID 或事务 ID)。**
- 进行对象跨层传递的时候,避免方法多次传递,打破层级间的约束。例如可以存储用于鉴权的token和用户信息等内容
- 线程间的数据隔离,例如线程上下文对象
- 用于存储事务信息
注意:⚠️
ThreadLocal并不是用于解决多线程下共享资源的技术。如果多线程的ThreadLocal存储了一个对象的引用,那么该引用还将面临资源竞争,数据不一致等并发问题
一个简单的使用测试
ThreadLocalThreadLocal如何实现线程私有副本的?tl = new ThreadLocal<>(); IntStream.range(0, 10).mapToObj(i -> new Thread(()-> { tl.set(i); try { TimeUnit.SECONDS.sleep(1); } catch (Exception e) { //do nothing! } System.out.printf("%s getTl Value %dn", Thread.currentThread().getName(), tl.get()); }, "Thread-" + i)) .forEach(Thread::start);
数据结构
ThreadLocal
- hash值
threadLocalHashCode = nextHashCode()
每个ThreadLocal实例都有系统全局唯一的hash值,hash值也将作为实际的键值用于在ThreadLocalMap中获取值
Thread
- tlMap对象
ThreadLocal.ThreadLocalMap threadLocals
当线程首次使用tl存储时,tl会在 线程对象中创建一个 tlMap, 并将存储在tlMap中,tlMap实际上是一个Entry[] 数组,初始容量为16。
ThreadLocalMap
- Entry数组
Entry[] table
Entry被设计为一个弱引用类(任意gc都将触发WeakReference对象的回收)
Entry
- key
- value
每一个线程对象都有一个threadLocalMap , 其中Map中的Entry数组的每一个元素对应一个ThreadLocal变量
由于tlMap是每个线程的成员变量,因而多线程环境下其必然是访问各自的tlMap,也就是文章开头说的私有变量副本。
那么tlMap被设计为一个Entry数组其目的是就某个线程而言,他可以使用多个ThreadLocal来存储不同的线程私有变量,多个tl作为tlMap的键值通过其唯一的hash值进一步进行hash计算得到其在tlMap中的索引。实际要存储的变量则作为值被存储到tlMap中。
关于tl的Hash值计算和索引hash计算总而言之 ⚠️
每一个线程都有一个tlMap变量,作为其私有的变量副本,这个私有变量副本是由线程访问的首个tl创建的
tl是线程私有变量副本中的键值,和线程私有变量组成一组键值对以弱引用Entry的形式被存储到副本中。
引申理解:
仓库里有一些储物柜,每个储物柜都需要一把钥匙, 每个储物柜都可以放东西。储物柜整个套着一个塑料袋,第一次用的时候要把塑料袋撕了,然后再用钥匙打开放东西。这个仓库就是Thread,储物柜是ThreadLocalMap,钥匙是ThreadLocal
请参考: https://www.jianshu.com/p/3c5d7f09dfbd 这篇文章写的也很好,给作者点赞



