ThreadLocal

TheadLocal有什么用?

ThreadLocal 类允许每个线程绑定自己的值,可以将其形象地比喻为一个“存放数据的盒子”。每个线程都有自己独立的盒子,用于存储私有数据,确保不同线程之间的数据互不干扰。

创建一个 ThreadLocal 变量时,每个访问该变量的线程都会拥有一个独立的副本。这也是 ThreadLocal 名称的由来。线程可以通过 get() 方法获取自己线程的本地副本,或通过 set() 方法修改该副本的值,从而避免了线程安全问题。

ThreadLocal 和 Synchronized 区别?

Synchronized 是基于锁机制的,用于控制对共享资源的访问,确保线程间数据的一致性和安全性,实现线程间的互斥访问。

所以说,Synchronized 是通过换空间让多个线程排队访问,ThreadLocal 是空间换时间为每个线程提供了一份变量的副本,从而实现线程隔离。

ThreadLocal原理

Thread类源代码。

1
2
3
4
5
6
7
8
9
public class Thread implements Runnable {
//......
//与此线程有关的ThreadLocal值。由ThreadLocal类维护
ThreadLocal.ThreadLocalMap threadLocals = null;

//与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
//......
}

Thread 类中有一个 threadLocals 和 一个 inheritableThreadLocals 变量,它们都是 ThreadLocalMap 类型的变量,我们可以把 ThreadLocalMap 理解为ThreadLocal 类实现的定制化的 HashMap。默认情况下这两个变量都是 null,只有当前线程调用 ThreadLocal 类的 setget方法时才创建它们,实际上调用这两个方法的时候,我们调用的是ThreadLocalMap类对应的 get()set()方法。

ThreadLocal类的set()方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void set(T value) {
//获取当前请求的线程
Thread t = Thread.currentThread();
//取出 Thread 类内部的 threadLocals 变量(哈希表结构)
ThreadLocalMap map = getMap(t);
if (map != null)
// 将需要存储的值放入到这个哈希表中
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

最终的变量是放在了当前线程的 ThreadLocalMap 中,并不是存在 ThreadLocal 上,ThreadLocal 可以理解为只是ThreadLocalMap的封装,传递了变量值。 ThrealLocal 类中可以通过Thread.currentThread()获取到当前线程对象后,直接通过getMap(Thread t)可以访问到该线程的ThreadLocalMap对象。

每个Thread中都具备一个ThreadLocalMap,而ThreadLocalMap可以存储以ThreadLocal为 key ,Object 对象为 value 的键值对。

1
2
3
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
//......
}

ThreadLocal 内存泄露问题是怎么导致的?

内存泄漏是由于无用的对象无法被GC回收,长时间占用内存,导致可用内存逐渐减少,最终形成内存溢出。

ThreadLocal 内存泄漏的根本原因在于其内部实现机制。

每个线程维护一个名为 ThreadLocalMap 的 map。 当你使用 ThreadLocal 存储值时,实际上是将值存储在当前线程的 ThreadLocalMap 中,其中 ThreadLocal 实例本身作为 key,而你要存储的值作为 value。

ThreadLocalMapkeyvalue 引用机制:

  • key 是弱引用:ThreadLocalMap 中的 key 是 ThreadLocal 的弱引用 (WeakReference<ThreadLocal<?>>)。 这意味着,如果 ThreadLocal 实例不再被任何强引用指向,垃圾回收器会在下次 GC 时回收该实例,导致 ThreadLocalMap 中对应的 key 变为 null

  • value 是强引用:ThreadLocalMap 中的 value 是强引用。 即使 key 被回收(变为 null),value 仍然存在于 ThreadLocalMap 中,被强引用,不会被回收。

ThreadLocal 实例失去强引用后,其对应的 value 仍然存在于 ThreadLocalMap 中,因为 Entry 对象强引用了它。如果线程持续存活(例如线程池中的线程),ThreadLocalMap 也会一直存在,导致 key 为 null 的 entry 无法被垃圾回收,机会造成内存泄漏。

内存泄漏的发生需要同时满足两个条件:

  1. ThreadLocal 实例不再被强引用;

  2. 线程持续存活,导致 ThreadLocalMap 长期存在。

ThreadLocalMap 如何解决 Hash 冲突

通过线性探测法,从头到尾去寻找下一个可用的位置,循环遍历。

如何跨线程传递ThreadLocal的值?

如果想要在异步场景下传递 ThreadLocal 值,有两种解决方案:

  • InheritableThreadLocalInheritableThreadLocal 是 JDK1.2 提供的工具,继承自 ThreadLocal 。使用 InheritableThreadLocal 时,会在创建子线程时,令子线程继承父线程中的 ThreadLocal 值,但是无法支持线程池场景下的 ThreadLocal 值传递。

  • TransmittableThreadLocalTransmittableThreadLocal (简称 TTL) 是阿里巴巴开源的工具类,继承并加强了InheritableThreadLocal类,可以在线程池的场景下支持 ThreadLocal 值传递。

InheritableThreadLocal 原理

Thread 类中添加了一个新的 ThreadLocalMap ,命名为 inheritableThreadLocals ,该变量用于存储需要跨线程传递的 ThreadLocal 值。

1
2
3
4
class Thread implements Runnable {
ThreadLocal.ThreadLocalMap threadLocals = null;
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

通过改造 Thread 类的构造方法来实现,在创建 Thread 线程时,拿到父线程的 inheritableThreadLocals 变量赋值给子线程即可。

TransmittableThreadLocal 原理

阿里巴巴无法改动 JDK 的源码,因此他内部通过 装饰器模式 在原有的功能上做增强,以此来实现线程池场景下的 ThreadLocal 值传递。

TTL 改造的地方有两处:

  • 实现自定义的 Thread ,在 run() 方法内部做 ThreadLocal 变量的赋值操作。

  • 基于 线程池 进行装饰,在 execute() 方法中,不提交 JDK 内部的 Thread ,而是提交自定义的 Thread


ThreadLocal
http://bloomivy.github.io/2025/01/29/ThreadLocal/
作者
Bloom
发布于
2025年1月29日
许可协议