什么是 ThreadLocal?
ThreadLocal 是 Java 中的一个工具类,用于为每个线程提供独立的变量副本,使得每个线程可以独立操作自己的变量,避免多线程环境下的数据竞争问题。它的核心思想是线程封闭(Thread Confinement),即通过将变量限制在线程内部使用,实现线程安全。
ThreadLocal 的核心机制
存储结构
每个线程(Thread 对象)内部维护了一个 ThreadLocalMap,键(Key)是 ThreadLocal 实例,值(Value)是该线程的局部变量副本。
java
// 简化的 ThreadLocal 结构
public class ThreadLocal<T> {
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = t.threadLocals;
Entry e = map.getEntry(this);
return (T)e.value;
}
}

ThreadLocalMap是ThreadLocal的内部静态类,而它的构成主要是用Entry来保存数据 ,而且还是继承的弱引用。在Entry内部使用ThreadLocal作为key,使用我们设置的value作为value。
数据隔离
不同线程访问同一个 ThreadLocal 时,实际上操作的是各自线程的 ThreadLocalMap 中的不同数据,天然线程安全。
ThreadLocal 的 API
void set(T value)
设置当前线程的变量副本
java
public void set(T value) {
//1、获取当前线程
Thread t = Thread.currentThread();
//2、获取线程中的属性 threadLocalMap ,如果threadLocalMap 不为空,
//则直接更新要保存的变量值,否则创建threadLocalMap,并赋值
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
// 初始化thradLocalMap 并赋值
createMap(t, value);
}
ThreadLocal set赋值的时候首先会获取当前线程thread,并获取thread线程中的ThreadLocalMap属性。如果map属性不为空,则直接更新value值,如果map为空,则实例化threadLocalMap,并将value值初始化
T get()
获取当前线程的变量副本
java
public T get() {
//1、获取当前线程
Thread t = Thread.currentThread();
//2、获取当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
//3、如果map数据不为空,
if (map != null) {
//3.1、获取threalLocalMap中存储的值
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
//如果是数据为null,则初始化,初始化的结果,TheralLocalMap中存放key值为threadLocal,值为null
return setInitialValue();
}
void remove()
移除当前线程的变量副本
java
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}
initialValue()
初始化变量(需重写,默认返回null)
java
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
ThreadLocal 的典型使用场景
全局变量在线程间隔离
例如用户会话(Session)、数据库连接、事务上下文等。
线程安全的工具类
如 SimpleDateFormat 是非线程安全的,通过 ThreadLocal 为每个线程分配独立实例:
java
private static final ThreadLocal<SimpleDateFormat> dateFormat =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
public String formatDate(Date date) {
return dateFormat.get().format(date);
}
跨方法传递隐式参数
避免在方法间层层传递参数,例如在日志框架中保存请求ID。
ThreadLocal 的内存泄漏问题
原因
ThreadLocalMap内部维护了一个Entry[] table来存储键值对的映射关系,内存泄漏和Entry类有非常大的关系,下面是Entry的源码:
java
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value; // 强引用
Entry(ThreadLocal<?> k, Object v) {
super(k); // Key 是弱引用
value = v;
}
}
ThreadLocalMap中的Entry的key是弱引用指向ThreadLocal实例,而value是强引用。(弱引用:非必须存活的对象,引用关系比软引用还弱,不管内存是否够用,下次GC一定回收)。
解决方案
自动清理机制(部分场景触发)
当调用 ThreadLocal 的 get()、set() 或 remove() 方法时,ThreadLocalMap 会尝试清理 Key 已被回收的 Entry(即 Key 为 null 的 Entry)。但这种清理是 非彻底 的:
触发条件:操作过程中发现 Key 为 null 的 Entry。
清理范围:仅清理当前操作路径上的 Entry,不会全量扫描。(从当前操作的哈希槽位(Hash Slot)开始,向后线性探测遍历,直到遇到 Entry 为 null 的槽位为止。在此路径上遇到的 所有 Key 为 null 的 Entry 会被直接清理,但其他位置的无效 Entry 可能仍残留。)
示例:
java
threadLocal.set(123); // 触发部分清理
threadLocal.get(); // 触发部分清理
显式调用 remove():
使用完 ThreadLocal 后,调用 remove() 清理 Entry。
使用 try-finally 块:
java
try {
threadLocal.set(value);
// ... 业务逻辑
} finally {
threadLocal.remove();
}
InheritableThreadLocal
默认情况下,子线程无法继承父线程的 ThreadLocal 变量。使用 InheritableThreadLocal 解决此问题:
java
private static final InheritableThreadLocal<String> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) {
inheritableThreadLocal.set("Parent Value");
new Thread(() -> {
System.out.println(inheritableThreadLocal.get()); // 输出: Parent Value
}).start();
}