0%

ThreadLocal线程本地存储

ThreadLocal线程本地存储详解

在并发编程中,处理共享数据时通常需要同步机制(如synchronizedLock)来保证线程安全。但 ThreadLocal 提供了另一种思路:让共享数据不再共享,通过为每个线程创建独立的变量副本,实现线程间的数据隔离。

ThreadLocal 核心原理

基本概念

ThreadLocal(线程本地变量)为每个线程维护一个独立的变量副本,线程对副本的修改不会影响其他线程。其核心思想是:

  • 空间换时间:通过为每个线程分配独立内存空间,避免线程间的同步开销。
  • 数据隔离:每个线程操作自己的副本,消除竞争条件。

实现结构

ThreadLocal 的实现依赖于Thread类和ThreadLocalMap

  • Thread 类:每个线程持有两个ThreadLocalMap对象:

    1
    2
    ThreadLocal.ThreadLocalMap threadLocals = null;       // 线程本地变量
    ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 可继承的本地变量
  • ThreadLocalMap:ThreadLocal 的静态内部类,本质是一个哈希表,key 为 ThreadLocal 对象,value 为线程的变量副本

ThreadLocal 核心方法

ThreadLocal 通过以下方法操作线程的变量副本:

get():获取当前线程的变量副本

1
2
3
4
5
6
7
8
9
10
11
12
13
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) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue(); // 若map为空,初始化并返回初始值
}

set(T value):设置当前线程的变量副本

1
2
3
4
5
6
7
8
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)

需重写此方法设置初始值,例如:

1
2
3
4
5
6
private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 0; // 初始值设为0
}
};

remove():移除当前线程的变量副本

1
2
3
4
5
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this); // 移除当前ThreadLocal对应的value
}

ThreadLocalMap 详解

ThreadLocalMap是 ThreadLocal 的核心存储结构,采用开地址法解决哈希冲突,内部由Entry数组实现。

Entry 结构

1
2
3
4
5
6
7
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到自身:

    1
    2
    3
    4
    // 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。

示例代码与运行结果

示例代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public class ThreadLocalVariableHolder {
private static ThreadLocal<Integer> myThreadLocal = new ThreadLocal<Integer>() {
@Override
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 {
@Override
public void run() {
for (int i = 0; i < 3; i++) {
ThreadLocalVariableHolder.increment();
System.out.println(Thread.currentThread().getName() + ":" + ThreadLocalVariableHolder.get());
Thread.yield();
}
}
}

运行结果

1
2
3
4
5
6
7
8
9
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 的隔离性。

欢迎关注我的其它发布渠道

表情 | 预览
快来做第一个评论的人吧~
Powered By Valine
v1.3.10