ThreadLocal线程本地存储
ThreadLocal线程本地存储详解
在并发编程中,处理共享数据时通常需要同步机制(如synchronized或Lock)来保证线程安全。但 ThreadLocal 提供了另一种思路:让共享数据不再共享,通过为每个线程创建独立的变量副本,实现线程间的数据隔离。
ThreadLocal 核心原理
基本概念
ThreadLocal(线程本地变量)为每个线程维护一个独立的变量副本,线程对副本的修改不会影响其他线程。其核心思想是:
- 空间换时间:通过为每个线程分配独立内存空间,避免线程间的同步开销。
- 数据隔离:每个线程操作自己的副本,消除竞争条件。
实现结构
ThreadLocal 的实现依赖于Thread类和ThreadLocalMap:
Thread 类:每个线程持有两个ThreadLocalMap对象:
ThreadLocal.ThreadLocalMap threadLocals = null; // 线程本地变量 ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 可继承的本地变量ThreadLocalMap:ThreadLocal 的静态内部类,本质是一个哈希表,key 为 ThreadLocal 对象,value 为线程的变量副本。
ThreadLocal 核心方法
ThreadLocal 通过以下方法操作线程的变量副本:
get():获取当前线程的变量副本
public T get() {
Thread t = Thread.currentThread(); // 获取当前线程
ThreadLocalMap map = getMap(t); // 获取线程的ThreadLocalMap
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 以当前ThreadLocal为key查询
if (e != null) {
T result = (T)e.value;
return result;
}
}
return setInitialValue(); // 若map为空,初始化并返回初始值
}
set(T value):设置当前线程的变量副本
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value); // 以当前ThreadLocal为key存储值
else
createMap(t, value); // 若map为空,初始化map
}
initialValue():初始化变量副本(默认 null)
需重写此方法设置初始值,例如:
private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0; // 初始值设为0
}
};
remove():移除当前线程的变量副本
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this); // 移除当前ThreadLocal对应的value
}
ThreadLocalMap 详解
ThreadLocalMap是 ThreadLocal 的核心存储结构,采用开地址法解决哈希冲突,内部由Entry数组实现。
Entry 结构
static class Entry extends WeakReference<ThreadLocal<?>> {
Object value; // 线程的变量副本
Entry(ThreadLocal<?> k, Object v) {
super(k); // key是弱引用(ThreadLocal对象)
value = v;
}
}
- key 为弱引用:当 ThreadLocal 对象无强引用时,会被 GC 回收,避免内存泄漏。
- value 为强引用:需手动调用
remove()释放,否则可能内存泄漏。
核心方法
set(ThreadLocal<?> key, Object value):存储键值对
- 计算 key 的哈希值确定数组下标。
- 若发生哈希冲突,采用线性探测法(依次检查下一个位置)寻找空闲槽位。
- 若遇到 key 为 null 的 Entry(已被 GC 回收),则替换该位置的无效数据。
getEntry(ThreadLocal<?> key):查询值
- 先通过哈希值定位下标,若命中直接返回。
- 若未命中,通过线性探测继续查找,同时清理 key 为 null 的无效 Entry。
remove(ThreadLocal<?> key):移除值
- 找到 key 对应的 Entry 后,清除其 value 并标记为 null,触发 GC 回收。
线程继承性:InheritableThreadLocal
ThreadLocal的变量无法被子线程继承,而InheritableThreadLocal(ThreadLocal 的子类)解决了这一问题,允许子线程访问父线程的本地变量。
实现原理
重写
getMap()和createMap(),使用线程的inheritableThreadLocals而非threadLocals。子线程创建时,会复制父线程的inheritableThreadLocals到自身:
// Thread类的init方法 if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
应用场景
- Web 开发中,用于在子线程中获取父线程的请求上下文(如
RequestContextHolder)。
内存泄漏问题

泄漏原因
- Entry 的 key 是弱引用,ThreadLocal 对象被回收后,key 变为 null。
- 但 value 是强引用,若线程未销毁(如线程池复用),value 会一直占用内存,导致泄漏。
解决方案
- 手动调用
remove():使用完 ThreadLocal 后,显式调用remove()清除 value。 - JDK 的防护机制:
get()、set()、remove()方法会自动清理 key 为 null 的 Entry。
示例代码与运行结果
示例代码
public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
protected Integer initialValue() {
return 0; // 初始值为0
}
};
public static void increment() {
myThreadLocal.set(myThreadLocal.get() + 1);
}
public static int get() {
return myThreadLocal.get();
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 3; i++) {
executorService.execute(new MyThread());
}
executorService.shutdown();
}
}
class MyThread implements Runnable {
public void run() {
for (int i = 0; i < 3; i++) {
ThreadLocalVariableHolder.increment();
System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalVariableHolder.get());
Thread.yield();
}
}
}
运行结果
pool-1-thread-1:1
pool-1-thread-2:1
pool-1-thread-3:1
pool-1-thread-1:2
pool-1-thread-2:2
pool-1-thread-3:2
pool-1-thread-1:3
pool-1-thread-2:3
pool-1-thread-3:3
每个线程的变量独立递增,互不干扰,验证了 ThreadLocal 的隔离性。