Thread Local
Thread Local
1.什么是 Thread Local ?
ThreadLocal 是 Java 中的一个类,用于在多线程环境下实现线程局部变量。简单来说,它提供了一种机制,可以使得每个线程都可以拥有自己独立的变量副本,而不必担心线程间的数据共享问题。
2.为什么需要 Thread Local?
- 在多线程环境下, 想要获取一个全局变量在不同线程中复用
- 在多线程环境下,防止自己的变量被其它线程篡改
3.使用 Tread Local
3.1 测试Tread Local
1 | |
3.2 结果
从这个结果我们可以发现,它是一个数据结构,有点像HashMap,可以保存”key : value“键值对,但是一个Thread Local只能保存一个,并且各个线程的数据互不干扰。
1 | |
4.Thread Local 源码分析
4.1 Set()方法执行流程
1 | |
- 首先,
ThreadLocal的set方法会获取当前线程,使用Thread.currentThread()方,来获取当前线程对象实例。 - 然后,通过
getMap(t)方法获取当前线程类的ThreadLocalMap。这个方法会根据当前线程来获取相应的ThreadLocalMap对象,如果当前线程没有对应的ThreadLocalMap,则会返回null。 - 接下来是关键的一步,在获取到当前线程的
ThreadLocalMap后,如果这个map不为null,说明当前线程已经有相关的ThreadLocalMap,则直接调用map.set(this, value)方法,将当前ThreadLocal对象和对应的值存入ThreadLocalMap中。 - 如果获取到的
ThreadLocalMap为null,说明当前线程还没有创建对应的ThreadLocalMap,则调用createMap(t, value)方法创建一个新的ThreadLocalMap并将当前ThreadLocal对象和对应的值存入其中。
4.2 Get()方法执行流程
1 | |
- 首先,通过
Thread.currentThread()获取当前线程对象实例。 - 然后,使用
getMap(t)方法获取当前线程对象实例对应的ThreadLocalMap。如果当前线程尚未创建ThreadLocalMap,则执行setInitialValue()来返回一个初始值null。 - 如果当前线程已经拥有
ThreadLocalMap,则调用map.getEntry(this)方法获取当前ThreadLocal实例对应的Entry对象。这个Entry对象包含了ThreadLocal实例和对应的值。 - 如果获取到的
Entry对象不为null,则将其对应的值强制类型转换为T类型并返回。这里使用了@SuppressWarnings("unchecked")注解来抑制未检查的类型转换警告,因为ThreadLocalMap的实现中value是用Object类型存储的。 - 如果在
ThreadLocalMap中没有找到与当前ThreadLocal实例关联的值(即获取的Entry为null),则调用setInitialValue()方法。这个方法用于设置初始值,通常在第一次调用get方法时会执行。
4.3 Thread Local Map 数据结构

ThreadLoalMap是一个类似HashMap的数据结构,但是在ThreadLocal中,并没实现Map接口。
在ThreadLoalMap中,也是初始化一个大小16的Entry数组,Entry对象用来保存每一个key-value键值对,只不过这里的key永远都是ThreadLocal对象,通过ThreadLocal对象的set方法,结果把ThreadLocal对象自己当做key,放进了ThreadLoalMap中。
这里需要注意的是,ThreadLoalMap的Entry是继承WeakReference,和HashMap很大的区别是,Entry中没有next字段,所以就不存在链表的情况了。
5. 哈希冲突
我们知道,当发生哈希冲突时,HashMap 会将具有相同哈希值的键值对放置在同一个桶(数组中的一个元素)中,并以链表结构(或树结构,从 JDK 8 开始)存储这些键值对。这样,每个桶可以容纳多个键值对,从而解决了哈希冲突的问题。
但是ThreadLocalMap只有数组结构 它是如何避免哈希冲突的呢?
5.1 Thread Local Map Set() 方法执行流程
先看看ThreadLoalMap中插入一个key-value的实现
1 | |
tab是table数组的引用,table是ThreadLocalMap中用于存储ThreadLocal变量的数组。len是table数组的长度。i是计算出的存储位置索引,通过对ThreadLocal的哈希值取模来确定存储位置,每个ThreadLocal对象都有一个hash值threadLocalHashCode,每初始化一个ThreadLocal对象,hash值就增加一个固定的大小0x61c88647。- 通过循环遍历
tab[i]开始的链表,如果发现已经存在相同的ThreadLocal对象,则更新对应的值为新值。 - 如果当前位置的
ThreadLocal对象为null,说明此位置的Entry已经失效,需要进行替换操作。 - 如果遍历结束还没有找到对应的
ThreadLocal对象,说明当前位置为空,需要新建一个Entry对象存储该键值对。 - 在设置完值之后,如果需要,会进行清理操作和扩容操作。
6.内存泄露
1 | |
Entry类是一个静态内部类。- 它继承自
WeakReference<ThreadLocal<?>>,这意味着Entry对象持有对ThreadLocal对象的弱引用。弱引用的特点是,当ThreadLocal对象没有强引用时,即没有其他对象持有它时,该ThreadLocal对象可以被垃圾回收。 Entry类包含了一个value字段,用于存储与ThreadLocal对象相关联的值。- 构造函数
Entry(ThreadLocal<?> k, Object v)接收两个参数:k表示ThreadLocal对象,v表示与之相关联的值。在构造Entry对象时,会调用super(k)来调用父类WeakReference的构造方法,将ThreadLocal对象作为参数传递进去,以创建对其的弱引用。同时,将v赋值给value字段。
x.x 结论
x.1 Thread Local 本身不存值
每个 ThreadLocal 实例都持有一个线程局部变量,这个变量的值是线程相关的,并且每个线程都有自己独立的这个值。因此,我们可以说 ThreadLocal 起到了存储值的作用,而线程实例起到了标识这个值的键的作用。
在ThreadLocalMap中的entry键值对存储的就是 Key ThreadLocal实例 和 其关联的值
x.2 Thread Local Map是线程自己的局部变量
ThreadLocalMap是每个线程自己的数据结构,用于存储当前线程与ThreadLocal实例关联的值。每个线程都有自己独立的ThreadLocalMap,这样就保证了线程之间的数据隔离性。
x.3 不适合处理大量数据
在高度冲突的情况下,set和get操作的效率可能会降低,因为需要不断地寻找下一个空位置或者匹配的Entry对象。这也提醒我们在使用ThreadLocal时要注意避免过多的冲突,可以通过合理设计ThreadLocal对象的哈希值来减少冲突的概率,或者考虑其他数据结构来代替ThreadLocal,以提高效率。
通过这种设计,我们可以在多线程环境下方便地将线程相关的值与线程关联起来,并且每个线程都可以独立地管理自己的线程本地变量,而不会受到其他线程的影响。