ThreadLocal 是 Java 并发编程中实现**线程封闭(Thread Confinement)**的核心工具。本文将从原理到实践,系统性地讲解 ThreadLocal 的设计哲学、内部机制、使用模式以及跨线程传递方案。

原理篇:ThreadLocal 的内部机制

核心设计理念:为什么不用 Map<Thread, Value>

很多人初次设计线程本地存储时,会想到用一个全局的 Map<Thread, Value> 来存储每个线程的数据。但这种设计有致命缺陷:Thread 对象会被 Map 强引用,导致线程无法被 JVM 回收,造成严重的内存泄漏。

ThreadLocal 采用了相反的设计:让 Thread 持有 Map,而不是让 Map 持有 Thread。每个 Thread 内部都有一个 ThreadLocalMap,用于存储该线程的所有线程本地变量。这样设计的好处是:

  • 线程销毁时,ThreadLocalMap 随之销毁,数据自动清理
  • ThreadLocal 对象可以被显式管理(如声明为静态变量)
  • 线程内部的存储容器是隐式的,由线程自己管理

引用关系结构

1
2
3
4
5
Thread -> ThreadLocalMap -> Entry[]

Entry extends WeakReference<ThreadLocal<?>>
- key: ThreadLocal 对象(弱引用)
- value: 实际存储的值(强引用)

一个 ThreadLocal 变量的"副本"实际上是分散存储在多个线程的 ThreadLocalMap 中的。每个线程的 ThreadLocalMap 中都有一个 Entry,以同一个 ThreadLocal 对象为 key,但 value 是各自独立的。

静态变量场景的引用关系:

1
2
方法区静态变量 -> 强引用 ThreadLocal 对象
ThreadLocalMap.Entry -> 弱引用 ThreadLocal 对象

四大核心原则

原则1:操作的本质

当我们调用 ThreadLocal.get()ThreadLocal.set() 时,实际上是在操作当前线程内部的 ThreadLocalMap。ThreadLocal 对象本身只是一个"访问入口",真正的数据存储在各个线程的隐藏 Map 中。

graph TB
    subgraph "方法区"
        TL[ThreadLocal 静态变量<br/>static ThreadLocal]
        style TL fill:#e1f5ff
    end
    
    subgraph "Thread-1 内部"
        T1[Thread-1]
        TLM1[ThreadLocalMap]
        E1[Entry]
        V1[value: data1]
        T1 -->|强引用| TLM1
        TLM1 -->|强引用| E1
        E1 -.->|key弱引用| TL
        E1 -->|强引用| V1
    end
    
    subgraph "Thread-2 内部"
        T2[Thread-2]
        TLM2[ThreadLocalMap]
        E2[Entry]
        V2[value: data2]
        T2 -->|强引用| TLM2
        TLM2 -->|强引用| E2
        E2 -.->|key弱引用| TL
        E2 -->|强引用| V2
    end
    
    TL -.->|"get()/set() 操作<br/>实际访问当前线程的 Map"| TLM1
    TL -.->|"get()/set() 操作<br/>实际访问当前线程的 Map"| TLM2

原则2:ThreadLocal 应该是 static 变量

ThreadLocal 应该声明为 static 变量,作为类级别的全局唯一实例。原因有三:

  1. 避免内存浪费:如果作为成员变量,每个对象实例都会创建新的 ThreadLocal,导致每个线程的 ThreadLocalMap 中会有多个 Entry,浪费内存且违背线程本地存储的设计初衷。我们的初衷是每个线程有一个变量的副本,而不是多个副本

  2. 防止对象无法回收:如果 ThreadLocal 是成员变量,开发者会习惯性地通过 对象实例.threadLocal.get() 来访问 ThreadLocal。这种使用方式会导致对象实例被外部持有引用,进而无法被 GC 回收。而如果 ThreadLocal 是 static 变量,访问方式是 类.threadLocal.get(),不会持有对象实例的引用,避免了对象级别的内存泄漏。

  3. Class 生命周期更长:相比之下,Class 对象通常不需要频繁回收,其生命周期与应用程序相当,因此 static 成员是可以接受的。static 声明确保全局只有一个 ThreadLocal 实例,每个线程的 Map 中只有一个对应的 Entry,既节省内存又避免了对象无法回收的问题。

graph TB
    subgraph "错误做法:ThreadLocal 作为成员变量"
        direction TB
        
        subgraph "对象实例1"
            O1[MyService 实例1]
            TL1[ThreadLocal 实例1]
            O1 -->|持有| TL1
            style O1 fill:#ffcdd2
            style TL1 fill:#ffcdd2
        end
        
        subgraph "对象实例2"
            O2[MyService 实例2]
            TL2[ThreadLocal 实例2]
            O2 -->|持有| TL2
            style O2 fill:#ffcdd2
            style TL2 fill:#ffcdd2
        end
        
        subgraph "对象实例3"
            O3[MyService 实例3]
            TL3[ThreadLocal 实例3]
            O3 -->|持有| TL3
            style O3 fill:#ffcdd2
            style TL3 fill:#ffcdd2
        end
        
        subgraph "Thread-1 的 Map"
            E1A[Entry: key=TL1]
            E1B[Entry: key=TL2]
            E1C[Entry: key=TL3]
            style E1A fill:#fff9c4
            style E1B fill:#fff9c4
            style E1C fill:#fff9c4
        end
        
        problem["问题:<br/>1. 每个对象实例都创建新的 ThreadLocal<br/>2. 线程的 Map 中会有多个 Entry<br/>3. 浪费内存,违背线程本地存储的设计初衷"]
        style problem fill:#ffebee
    end
    
    subgraph "正确做法:ThreadLocal 作为 static 变量"
        direction TB
        
        subgraph "类级别"
            TLS[static ThreadLocal<br/>全局唯一实例]
            style TLS fill:#c8e6c9
        end
        
        subgraph "Thread-1 的 Map"
            E2[Entry: key=TLS<br/>唯一的 Entry]
            E2 -.->|key弱引用| TLS
            style E2 fill:#e8f5e9
        end
        
        subgraph "Thread-2 的 Map"
            E3[Entry: key=TLS<br/>唯一的 Entry]
            E3 -.->|key弱引用| TLS
            style E3 fill:#e8f5e9
        end
        
        benefit["优势:<br/>1. 全局只有一个 ThreadLocal 实例<br/>2. 每个线程的 Map 中只有一个对应的 Entry<br/>3. 节省内存,符合设计初衷"]
        style benefit fill:#e8f5e9
    end

原则3:线程生命周期决定数据生命周期

即使我们不主动调用 remove(),当线程销毁时(如普通线程执行完毕),该线程的 ThreadLocalMap 也会随之销毁,所有 value 自动被回收。这就是为什么在非线程池场景下,ThreadLocal 的内存泄漏问题不那么严重。

graph TB
    subgraph "场景:普通线程执行完毕"
        direction TB
        
        subgraph "执行前"
            T1A[Thread-1 运行中]
            TLM1A[ThreadLocalMap]
            E1A[Entry]
            V1A[value: data1]
            T1A -->|强引用| TLM1A
            TLM1A -->|强引用| E1A
            E1A -->|强引用| V1A
            style T1A fill:#fff4e6
            style TLM1A fill:#f0f0f0
            style E1A fill:#e8f5e9
            style V1A fill:#c8e6c9
        end
        
        arrow1["线程执行完毕"]
        style arrow1 fill:#ffebee
        
        subgraph "执行后"
            destroyed["Thread-1 被销毁<br/>ThreadLocalMap 被销毁<br/>Entry 被销毁<br/>value 被 GC 回收"]
            style destroyed fill:#ffcdd2
        end
        
        执行前 --> arrow1
        arrow1 --> 执行后
    end

原则4:ThreadLocal 对象的生命周期影响所有线程

如果我们将 ThreadLocal 静态变量置为 null(去除强引用),那么所有线程的 ThreadLocalMap 中对应的 Entry 的 key 都会失效(弱引用被回收)。即使线程什么都不做,只要后续有任何 get/set/remove 操作触发,这些 key 为 null 的 Entry 就会被自动清理,value 随之消失。

graph TB
    subgraph "场景:ThreadLocal 静态变量被置为 null"
        direction TB
        
        subgraph "置为 null 前"
            TLA[ThreadLocal 静态变量]
            E1A[Thread-1 的 Entry]
            E2A[Thread-2 的 Entry]
            E3A[Thread-3 的 Entry]
            V1A[value: data1]
            V2A[value: data2]
            V3A[value: data3]
            E1A -.->|key弱引用| TLA
            E2A -.->|key弱引用| TLA
            E3A -.->|key弱引用| TLA
            E1A -->|强引用| V1A
            E2A -->|强引用| V2A
            E3A -->|强引用| V3A
            style TLA fill:#e1f5ff
            style E1A fill:#e8f5e9
            style E2A fill:#e8f5e9
            style E3A fill:#e8f5e9
            style V1A fill:#c8e6c9
            style V2A fill:#c8e6c9
            style V3A fill:#c8e6c9
        end
        
        arrow2["ThreadLocal = null + GC"]
        style arrow2 fill:#ffebee
        
        subgraph "置为 null 后"
            TLB[ThreadLocal 对象被 GC 回收]
            E1B[Thread-1 的 Entry<br/>key=null]
            E2B[Thread-2 的 Entry<br/>key=null]
            E3B[Thread-3 的 Entry<br/>key=null]
            V1B[value: data1<br/>待清理]
            V2B[value: data2<br/>待清理]
            V3B[value: data3<br/>待清理]
            E1B -->|强引用| V1B
            E2B -->|强引用| V2B
            E3B -->|强引用| V3B
            style TLB fill:#ffcdd2
            style E1B fill:#fff9c4
            style E2B fill:#fff9c4
            style E3B fill:#fff9c4
            style V1B fill:#ffebee
            style V2B fill:#ffebee
            style V3B fill:#ffebee
        end
        
        arrow3["任意线程调用 get/set/remove"]
        style arrow3 fill:#ffebee
        
        subgraph "自动清理"
            cleaned["expungeStaleEntry 触发<br/>key=null 的 Entry 被清理<br/>value 被 GC 回收"]
            style cleaned fill:#c8e6c9
        end
        
        置为null前 --> arrow2
        arrow2 --> 置为null后
        置为null后 --> arrow3
        arrow3 --> 自动清理
    end

为什么 key 使用弱引用?

因为线程内部的 ThreadLocalMap 是隐式容器,由线程自己管理。如果 key 使用强引用,那么只要线程存活(如线程池场景),ThreadLocal 对象就永远无法被回收。使用弱引用后,当 ThreadLocal 对象没有外部强引用时(如静态变量被置为 null),它可以被 GC 回收,Entry 的 key 变为 null,后续的 get/set/remove 操作会自动清理这些过期的 Entry。

弱引用的特性:在垃圾回收器线程扫描内存区域时,一旦发现只具有弱引用的对象,不管当前内存空间是否足够,都会回收它的内存。

Stale Entry 的自动清理机制

当 ThreadLocal 对象失去强引用后,GC 会回收它,此时 Entry 中的弱引用 key 会变成 null。但 Entry 本身仍然占据 ThreadLocalMap 的槽位,value 也仍然被 Entry 强引用。这种 key 为 null 的 Entry 被称为 Stale Entry(过期条目)

ThreadLocalMap 的 get()set()remove() 方法在执行过程中,会主动检测并清理这些 Stale Entry。这是通过调用 expungeStaleEntry() 方法实现的:

sequenceDiagram
    participant App as 应用代码
    participant TL as ThreadLocal
    participant TLM as ThreadLocalMap
    participant Entry as Entry[]
    participant GC as GC
    
    Note over App,GC: 阶段1:正常使用
    App->>TL: threadLocal.set(value)
    TL->>TLM: set(this, value)
    TLM->>Entry: table[i] = new Entry(key, value)
    Note over Entry: Entry.key = WeakRef(ThreadLocal)<br/>Entry.value = value(强引用)
    
    Note over App,GC: 阶段2:ThreadLocal 失去强引用
    App->>App: threadLocal = null
    Note over TL: 只剩 Entry 的弱引用指向 ThreadLocal
    
    Note over App,GC: 阶段3:GC 回收 ThreadLocal
    GC->>TL: 回收 ThreadLocal 对象
    Note over Entry: Entry.key.get() == null<br/>Entry.value 仍然存在(Stale Entry)
    
    Note over App,GC: 阶段4:后续操作触发清理
    App->>TL: anotherThreadLocal.get()
    TL->>TLM: getEntry(this)
    
    TLM->>TLM: 遍历 table 寻找目标 Entry
    
    alt 遇到 Stale Entry (key == null)
        TLM->>TLM: expungeStaleEntry(staleSlot)
        Note over TLM: 1. 将 Entry.value 置为 null<br/>2. 将 Entry 置为 null<br/>3. rehash 后续元素
        TLM->>Entry: table[staleSlot] = null
        Note over Entry: value 失去引用,可被 GC 回收
    end
    
    TLM-->>TL: 返回目标 Entry 的 value
    TL-->>App: 返回 value

为什么 get/set/remove 能"知道" Entry 已过期?

这是弱引用的核心特性:WeakReference.get() 方法会返回被引用的对象,但如果该对象已被 GC 回收,则返回 null。ThreadLocalMap 的 Entry 继承自 WeakReference<ThreadLocal<?>>,因此:

  1. 当 ThreadLocal 对象存活时:entry.get() 返回 ThreadLocal 对象。
  2. 当 ThreadLocal 对象被 GC 回收后:entry.get() 返回 null——这就是继承 WeakReference 的好处。
graph TB
    subgraph "Entry 状态判断"
        CHECK["entry.get()"]
        
        VALID["返回 ThreadLocal 对象<br/>━━━━━━━━━━━━━━━━━━━━<br/>Entry 有效<br/>正常读取/更新 value"]
        
        STALE["返回 null<br/>━━━━━━━━━━━━━━━━━━━━<br/>Entry 过期(Stale)<br/>触发 expungeStaleEntry()"]
        
        CHECK -->|"ThreadLocal 存活"| VALID
        CHECK -->|"ThreadLocal 已被 GC"| STALE
        
        style VALID fill:#c8e6c9
        style STALE fill:#ffcdd2
    end

清理的时机与局限性:

操作 是否触发清理 清理范围
get() 遍历过程中遇到的 Stale Entry
set() 遍历过程中遇到的 Stale Entry + 可能触发全表扫描
remove() 遍历过程中遇到的 Stale Entry
无任何操作 这就是泄漏发生的根本原因

关键结论:自动清理机制是被动触发的,只有在调用 get/set/remove 时才会执行。如果线程长期存活(如线程池)且不再访问任何 ThreadLocal,那些 Stale Entry 将永远不会被清理,导致内存泄漏。

关键源码解析(JDK 8):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ThreadLocalMap.getEntry() 方法
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key) // e.get() 获取弱引用指向的对象
return e;
else
return getEntryAfterMiss(key, i, e); // 触发清理逻辑
}

// 核心清理方法
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;

// 清理当前 Stale Entry
tab[staleSlot].value = null; // 断开 value 的强引用
tab[staleSlot] = null; // 断开 Entry 的引用
size--;

// 继续向后扫描,清理更多 Stale Entry 并 rehash
// ...
}

ThreadLocal 核心方法调用链解析

理解 ThreadLocal 的工作原理,关键在于理清各个方法之间的调用关系。下面我们从源码层面逐一分析。

方法调用关系图

graph TB
    subgraph "ThreadLocal 外部 API"
        SET["ThreadLocal.set(T value)"]
        GET["ThreadLocal.get()"]
        REMOVE["ThreadLocal.remove()"]
    end
    
    subgraph "ThreadLocal 内部方法"
        GETMAP["getMap(Thread t)"]
        CREATEMAP["createMap(Thread t, T firstValue)"]
        SETINITIAL["setInitialValue()"]
    end
    
    subgraph "ThreadLocalMap 内部方法"
        MAPSET["ThreadLocalMap.set(ThreadLocal key, Object value)"]
        GETENTRY["ThreadLocalMap.getEntry(ThreadLocal key)"]
        GETMISS["getEntryAfterMiss(ThreadLocal key, int i, Entry e)"]
        MAPREMOVE["ThreadLocalMap.remove(ThreadLocal key)"]
        EXPUNGE["expungeStaleEntry(int staleSlot)"]
        REPLACE["replaceStaleEntry(...)"]
        CLEAN["cleanSomeSlots(...)"]
        MAPCONSTRUCTOR["ThreadLocalMap(ThreadLocal firstKey, Object firstValue)"]
    end
    
    SET --> GETMAP
    SET -->|"map != null"| MAPSET
    SET -->|"map == null"| CREATEMAP
    CREATEMAP --> MAPCONSTRUCTOR
    
    GET --> GETMAP
    GET -->|"map != null"| GETENTRY
    GET -->|"map == null 或 entry == null"| SETINITIAL
    GETENTRY -->|"hash 冲突"| GETMISS
    GETMISS --> EXPUNGE
    SETINITIAL --> CREATEMAP
    SETINITIAL --> MAPSET
    
    REMOVE --> GETMAP
    REMOVE --> MAPREMOVE
    MAPREMOVE --> EXPUNGE
    
    MAPSET -->|"遇到 stale entry"| REPLACE
    REPLACE --> EXPUNGE
    MAPSET --> CLEAN
    CLEAN --> EXPUNGE
    
    style SET fill:#e1f5ff
    style GET fill:#e1f5ff
    style REMOVE fill:#e1f5ff
    style EXPUNGE fill:#ffcdd2

核心方法源码解析

1. ThreadLocal.set(T value) —— 设置入口

1
2
3
4
5
6
7
8
9
10
11
12
public void set(T value) {
// 1. 获取当前线程
Thread t = Thread.currentThread();
// 2. 获取当前线程的 ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
// 3a. Map 已存在,直接设置
map.set(this, value);
else
// 3b. Map 不存在,创建并设置第一个值
createMap(t, value);
}

设计要点set() 方法体现了懒加载思想——只有在首次调用 set() 时才创建 ThreadLocalMap。

2. getMap(Thread t) —— 获取线程的 Map

1
2
3
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}

设计要点:这个方法揭示了 ThreadLocal 的核心设计——Map 存储在 Thread 对象内部,而不是 ThreadLocal 对象内部。这是"让 Thread 持有 Map,而不是让 Map 持有 Thread"设计理念的直接体现。

3. createMap(Thread t, T firstValue) —— 创建 Map

1
2
3
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

设计要点:创建 Map 时直接传入第一个键值对,避免了"先创建空 Map,再插入"的两步操作。

4. ThreadLocalMap 构造函数

1
2
3
4
5
6
7
8
9
10
11
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
// 1. 初始化 Entry 数组,初始容量为 16
table = new Entry[INITIAL_CAPACITY];
// 2. 计算第一个 Entry 的位置(使用魔数 0x61c88647 保证哈希分布均匀)
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
// 3. 创建 Entry 并放入数组
table[i] = new Entry(firstKey, firstValue);
size = 1;
// 4. 设置扩容阈值(容量的 2/3)
setThreshold(INITIAL_CAPACITY);
}

设计要点

  • 初始容量 16,与 HashMap 相同
  • 使用 0x61c88647(黄金分割数)作为哈希增量,使 Entry 分布更均匀
  • 扩容阈值为容量的 2/3,比 HashMap 的 0.75 更保守,减少哈希冲突

5. ThreadLocalMap.set(ThreadLocal<?> key, Object value) —— 核心设置逻辑

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
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;
// 1. 计算初始位置
int i = key.threadLocalHashCode & (len - 1);

// 2. 线性探测
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); // 获取弱引用指向的 ThreadLocal

// 2a. 找到相同的 key,更新 value
if (k == key) {
e.value = value;
return;
}

// 2b. 发现 Stale Entry(key 已被 GC),替换它
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}

// 3. 找到空槽位,创建新 Entry
tab[i] = new Entry(key, value);
int sz = ++size;

// 4. 尝试清理一些 Stale Entry,如果没清理掉且超过阈值则扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}

设计要点

  • 使用线性探测解决哈希冲突
  • 在探测过程中顺便清理 Stale Entry(k == null 的情况)
  • cleanSomeSlots() 是启发式清理,不会扫描全表

6. ThreadLocal.get() —— 获取入口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
// 1. 尝试获取 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}
// 2. Map 不存在或 Entry 不存在,初始化
return setInitialValue();
}

7. getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) —— 哈希冲突时的查找

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;

// 线性探测查找
while (e != null) {
ThreadLocal<?> k = e.get();
if (k == key)
return e; // 找到了
if (k == null)
expungeStaleEntry(i); // 顺便清理 Stale Entry
else
i = nextIndex(i, len); // 继续探测
e = tab[i];
}
return null; // 没找到
}

设计要点:在查找过程中顺便清理遇到的 Stale Entry,这是"惰性清理"策略的体现。

8. ThreadLocal.remove() 和 ThreadLocalMap.remove(ThreadLocal<?> key)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
// ThreadLocal.remove()
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null)
m.remove(this);
}

// ThreadLocalMap.remove()
private void remove(ThreadLocal<?> key) {
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len - 1);

for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
if (e.get() == key) {
e.clear(); // 清除弱引用
expungeStaleEntry(i); // 清理该位置并 rehash 后续元素
return;
}
}
}

9. expungeStaleEntry(int staleSlot) —— 核心清理方法

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
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;

// 1. 清理目标位置的 Stale Entry
tab[staleSlot].value = null; // 断开 value 的强引用,使其可被 GC
tab[staleSlot] = null; // 断开 Entry 的引用
size--;

// 2. 继续向后扫描,直到遇到空槽位
Entry e;
int i;
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();
if (k == null) {
// 2a. 又发现一个 Stale Entry,清理它
e.value = null;
tab[i] = null;
size--;
} else {
// 2b. 有效 Entry,检查是否需要 rehash
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
// 该 Entry 不在其理想位置,需要重新放置
tab[i] = null;
// 找到一个空槽位放置
while (tab[h] != null)
h = nextIndex(h, len);
tab[h] = e;
}
}
}
return i; // 返回扫描结束的位置
}

设计要点

  • 参数 staleSlot已知的 Stale Entry 位置
  • 不仅清理目标位置,还会继续向后扫描清理更多 Stale Entry
  • 对有效 Entry 进行 rehash,确保线性探测链不断裂
  • 返回值是扫描结束的位置,供调用者使用

方法调用关系总结

外部 API 调用的内部方法 可能触发的清理方法
set(T) getMap()ThreadLocalMap.set()createMap() replaceStaleEntry()expungeStaleEntry()
cleanSomeSlots()expungeStaleEntry()
get() getMap()getEntry()getEntryAfterMiss()setInitialValue() expungeStaleEntry()
remove() getMap()ThreadLocalMap.remove() expungeStaleEntry()

核心结论expungeStaleEntry() 是所有清理操作的最终执行者,而 get()set()remove() 都会在执行过程中触发它,实现"惰性清理"。

为什么 ThreadLocalMap 使用开放地址法而不是链表法?

HashMap 使用"数组 + 链表/红黑树"的结构来处理哈希冲突,而 ThreadLocalMap 却选择了开放地址法(线性探测)。这个设计选择背后有深刻的考量:

对比维度 HashMap(链表法) ThreadLocalMap(开放地址法)
冲突处理 冲突的元素挂在同一个桶的链表上 冲突时向后探测下一个空槽位
内存布局 链表节点分散在堆中,缓存不友好 所有 Entry 在连续数组中,缓存友好
空间开销 每个节点需要额外的 next 指针 无额外指针开销
删除操作 简单的链表节点删除 需要 rehash 后续元素(复杂)

ThreadLocalMap 选择开放地址法的核心原因:

  1. Entry 数量通常很少:一个线程的 ThreadLocalMap 中通常只有几个到几十个 Entry(对应几个 ThreadLocal 变量),远少于 HashMap 的典型使用场景。在元素少的情况下,开放地址法的线性探测效率很高。

  2. 弱引用清理的需要:ThreadLocalMap 的 key 是弱引用,需要在遍历过程中发现并清理 Stale Entry。开放地址法的线性探测天然支持这种"顺便清理"的模式——在查找目标 Entry 的过程中,可以顺便清理沿途遇到的 Stale Entry。

  3. 缓存友好性:开放地址法将所有 Entry 存储在连续的数组中,CPU 缓存预取效果好。对于频繁访问的 ThreadLocal(如每次请求都要读取的上下文信息),缓存友好性带来的性能提升是显著的。

内存泄漏的发生机制

ThreadLocal 的内存泄漏实际上是一个条件链,任何一个环节被破坏都可能导致泄漏:

  1. ThreadLocal 对象被回收:当 ThreadLocal 对象没有强引用时(如静态变量被置为 null),它会被 GC 回收
  2. Entry 的 key 变为 null:ThreadLocalMap 中对应的 Entry 的 key(弱引用)失效
  3. value 仍被强引用:但 value 仍被 Entry 强引用,无法被回收(value 不是被 key 引用,而是被 Entry 引用
  4. 自动清理机制:后续的 get/set/remove 操作会触发 expungeStaleEntry(),清理 key 为 null 的 Entry
  5. 泄漏发生:如果线程长期存活(如线程池)且不再调用 get/set/remove,这些 Entry 永远不会被清理

Value 泄漏的因果链

1
2
3
4
5
ThreadLocal 对象失去强引用 
Entry.key (WeakReference) 被 GC 回收变成 null
→ 但 Entry 本身还在 ThreadLocalMap 中(Entry 泄漏/Stale Entry
Entry 持有 value 的强引用
→ value 无法被回收(Value 泄漏)

结论:Value 泄漏是 Entry 泄漏的直接后果。更准确地说,是因为 Stale Entry 没有被及时清理——ThreadLocal 对象本身是可以被回收的(因为是弱引用),问题在于回收后遗留的 Stale Entry 没有被清理。

实践篇:ThreadLocal 的使用模式

基础版本:静态工具类封装

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
36
37
// 定义基础的 Context 类型
public class ServerContext {
private String traceId;
private String userId;
// getters and setters
}

// 为这个基础类型装载一个 ThreadLocal 容器,然后为这个容器准备一个静态工具类
public class ContextHolder {
// ThreadLocal 必须是 static 的
private static final ThreadLocal<ServerContext> SERVER_CONTEXT = new ThreadLocal<>();

public static ServerContext getServerContext() {
return SERVER_CONTEXT.get();
}

public static void setServerContext(ServerContext context) {
SERVER_CONTEXT.set(context);
}

public static void clear() {
SERVER_CONTEXT.remove();
}
}

// 一个更大的 context,来汇总各种容器工具类
public class BizContext {

public static void setCurrentServerContext(final ServerContext context) {
// 注意这里把 null 拿来 clear,而不是直接 put 的逻辑
if (context == null) {
ContextHolder.clear();
} else {
ContextHolder.setServerContext(context);
}
}
}

用 Map 来取消第一层工具类的方案

这种方案使用一个 Map 来存储多种类型的上下文,但Map 容易腐化,需要谨慎使用:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class ContextFactory {

protected ContextFactory() {
// 私有构造器
}

/**
* 上下文持有器
* 全局静态变量,保存真正的交易上下文。
* withInitial 没有被重载过,必须赋值给 ThreadLocal 类型。
*/
private static final ThreadLocal<Map<String, Object>> CONTEXT_HOLDER =
InheritableThreadLocal.withInitial(() -> new ConcurrentHashMap<>(16));

public static void clear() {
CONTEXT_HOLDER.remove();
}

public static ThreadLocal<Map<String, Object>> getContextHolder() {
return CONTEXT_HOLDER;
}
}

public class TransactionContextFactory extends ContextFactory {

private TransactionContextFactory() {
throw new UnsupportedOperationException();
}

private static final String TRANSACTION_CONTEXT_KEY = "TransactionContext";

/**
* 静态工厂方法,获取交易上下文实例。
* 因为线程封闭和线程隔离,这里无需加锁和 double check
*/
@SuppressWarnings("unchecked")
public static <T> TransactionContext<T> getTransactionContext() {
Map<String, Object> realContextHolder = getContextHolder().get();
if (null == realContextHolder) {
realContextHolder = new ConcurrentHashMap<>(16);
getContextHolder().set(realContextHolder);
}

TransactionContext<T> realContext;
Object mapValue = realContextHolder.get(TRANSACTION_CONTEXT_KEY);
if (mapValue instanceof TransactionContext) {
realContext = (TransactionContext<T>) mapValue;
} else {
realContext = new TransactionContext<>();
realContextHolder.put(TRANSACTION_CONTEXT_KEY, realContext);
}
return realContext;
}
}

对这个 Map 的加强版本——不可变 Map 模式

1
2
3
4
5
6
7
8
9
10
11
@Override
public void put(final String key, final String value) {
if (!useMap) {
return;
}
Map<String, String> map = localMap.get();
// 每次修改都创建新的不可变 Map,保证线程安全
map = map == null ? new HashMap<>(1) : new HashMap<>(map);
map.put(key, value);
localMap.set(Collections.unmodifiableMap(map));
}

绑定容器到线程并保存上一个状态

这是 Spring 事务管理中使用的经典模式——栈式上下文管理

1
2
3
4
5
6
7
8
9
10
11
private void bindToThread() {
// 保存当前的 TransactionInfo,以便事务完成后恢复
this.oldTransactionInfo = transactionInfoHolder.get();
transactionInfoHolder.set(this);
}

private void restoreThreadLocalStatus() {
// 使用栈来恢复旧的 TransactionInfo
// 如果没有旧的,则为 null
transactionInfoHolder.set(this.oldTransactionInfo);
}

ThreadLocal 变策略模式

Spring Security 的 SecurityContextHolder 是一个经典的策略模式实现,支持三种存储策略:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// 策略接口
public interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}

// InheritableThreadLocal 策略实现
final class InheritableThreadLocalSecurityContextHolderStrategy implements
SecurityContextHolderStrategy {

private static final ThreadLocal<SecurityContext> contextHolder =
new InheritableThreadLocal<>();

public void clearContext() {
contextHolder.remove();
}

public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}

public void setContext(SecurityContext context) {
Assert.notNull(context, "Only non-null SecurityContext instances are permitted");
contextHolder.set(context);
}

public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}

// 策略持有者
public class SecurityContextHolder {

public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";

private static String strategyName = System.getProperty("spring.security.strategy");
private static SecurityContextHolderStrategy strategy;

static {
initialize();
}

private static void initialize() {
if (!StringUtils.hasText(strategyName)) {
strategyName = MODE_THREADLOCAL;
}

if (strategyName.equals(MODE_THREADLOCAL)) {
strategy = new ThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
} else if (strategyName.equals(MODE_GLOBAL)) {
strategy = new GlobalSecurityContextHolderStrategy();
} else {
// 尝试加载自定义策略
try {
Class<?> clazz = Class.forName(strategyName);
Constructor<?> customStrategy = clazz.getConstructor();
strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
} catch (Exception ex) {
ReflectionUtils.handleReflectionException(ex);
}
}
}

public static void clearContext() {
strategy.clearContext();
}

public static SecurityContext getContext() {
return strategy.getContext();
}

public static void setContext(SecurityContext context) {
strategy.setContext(context);
}
}

带名字的 ThreadLocal

Spring 提供的 NamedThreadLocal,便于调试和诊断:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class NamedThreadLocal<T> extends ThreadLocal<T> {

private final String name;

public NamedThreadLocal(String name) {
Assert.hasText(name, "Name must not be empty");
this.name = name;
}

@Override
public String toString() {
return this.name;
}
}

跨线程传递篇:InheritableThreadLocal 与 TransmittableThreadLocal

InheritableThreadLocal 的工作原理

Thread 类的双 Map 设计

Thread 类里面其实存在两个 ThreadLocalMap:

1
2
3
4
5
6
7
8
public class Thread implements Runnable {
// 当前线程的 ThreadLocalMap,主要存储该线程自身的 ThreadLocal
ThreadLocal.ThreadLocalMap threadLocals = null;

// InheritableThreadLocal,自父线程继承而来的 ThreadLocalMap
// 主要用于父子线程间 ThreadLocal 变量的传递
ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
}

InheritableThreadLocal 的极简实现

令人惊讶的是,InheritableThreadLocal 只有 3 个方法,却实现了完整的父子线程值传递功能:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class InheritableThreadLocal<T> extends ThreadLocal<T> {

// 1. 子线程继承时调用,可重写以修改继承的值
protected T childValue(T parentValue) {
return parentValue; // 默认直接返回父线程的值
}

// 2. 重写 getMap,返回 inheritableThreadLocals 而非 threadLocals
ThreadLocalMap getMap(Thread t) {
return t.inheritableThreadLocals;
}

// 3. 重写 createMap,创建 inheritableThreadLocals 而非 threadLocals
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
}

设计精妙之处:通过重写 getMap()createMap() 两个方法,将所有 InheritableThreadLocal 的值存储在独立的 inheritableThreadLocals Map 中,与普通 ThreadLocal 完全隔离。这样在创建子线程时,只需复制 inheritableThreadLocals,而不影响 threadLocals

Thread.init() 方法逐行解析

线程的构造器里隐藏着继承的核心逻辑。下面是 Thread.init() 方法的逐行中文注释

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
/**
* 初始化一个线程
* @param g 线程组
* @param target 要执行的 Runnable
* @param name 线程名称
* @param stackSize 栈大小(0 表示忽略)
* @param acc 访问控制上下文
* @param inheritThreadLocals 是否继承父线程的 InheritableThreadLocal
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {

// 1. 线程名不能为 null
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;

// 2. 获取当前线程作为"父线程"
// 关键:创建子线程的线程就是父线程
Thread parent = currentThread();

// 3. 安全管理器检查
SecurityManager security = System.getSecurityManager();
if (g == null) {
// 如果没有指定线程组,尝试从安全管理器获取
if (security != null) {
g = security.getThreadGroup();
}
// 如果还是没有,使用父线程的线程组
if (g == null) {
g = parent.getThreadGroup();
}
}

// 4. 检查是否有权限访问该线程组
g.checkAccess();

// 5. 检查是否有权限创建子类(如果是 Thread 的子类)
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}

// 6. 将线程添加到线程组(未启动状态)
g.addUnstarted();

// 7. 设置线程组
this.group = g;

// 8. 继承父线程的 daemon 属性
// 如果父线程是守护线程,子线程也是守护线程
this.daemon = parent.isDaemon();

// 9. 继承父线程的优先级
this.priority = parent.getPriority();

// 10. 设置上下文类加载器
if (security == null || isCCLOverridden(parent.getClass())) {
this.contextClassLoader = parent.getContextClassLoader();
} else {
this.contextClassLoader = parent.contextClassLoader;
}

// 11. 设置访问控制上下文
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();

// 12. 设置要执行的 Runnable
this.target = target;

// 13. 应用优先级设置
setPriority(priority);

// ========== 关键代码:InheritableThreadLocal 的继承 ==========
// 14. 如果允许继承且父线程有 inheritableThreadLocals
if (inheritThreadLocals && parent.inheritableThreadLocals != null) {
// 创建子线程的 inheritableThreadLocals,复制父线程的所有值
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
}
// ============================================================

// 15. 设置栈大小
this.stackSize = stackSize;

// 16. 分配线程 ID
tid = nextThreadID();
}

继承流程图解

sequenceDiagram
    participant Main as 主线程
    participant Thread as Thread 类
    participant Child as 子线程
    participant ITL as InheritableThreadLocal
    
    Note over Main: 主线程设置 ITL 值
    Main->>ITL: set("parent-value")
    ITL->>Main: 存入 inheritableThreadLocals
    
    Note over Main,Child: 创建子线程
    Main->>Thread: new Thread(runnable)
    Thread->>Thread: init(..., inheritThreadLocals=true)
    
    Note over Thread: init() 方法执行
    Thread->>Main: parent = currentThread()
    Thread->>Main: 检查 parent.inheritableThreadLocals
    
    alt parent.inheritableThreadLocals != null
        Thread->>Thread: createInheritedMap(parent.inheritableThreadLocals)
        Note over Thread: 遍历父线程的 Map<br/>对每个 Entry 调用 childValue()<br/>创建子线程的 Map
        Thread->>Child: this.inheritableThreadLocals = 新 Map
    end
    
    Note over Child: 子线程启动后
    Child->>ITL: get()
    ITL->>Child: 从 inheritableThreadLocals 获取
    Child-->>Child: 返回 "parent-value"

Thread 为 InheritableThreadLocal 的专门改造

问题:Thread 类是否为 InheritableThreadLocal 专门改造过?

答案:是的,Thread 类进行了以下专门改造:

  1. 新增字段inheritableThreadLocals 字段专门用于存储可继承的 ThreadLocal 值
  2. init() 方法增强:添加了 inheritThreadLocals 参数和复制逻辑
  3. createInheritedMap() 方法:ThreadLocal 类中专门提供了创建继承 Map 的静态方法
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
// ThreadLocal.createInheritedMap() 源码
static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) {
return new ThreadLocalMap(parentMap);
}

// ThreadLocalMap 的私有构造函数,专门用于继承
private ThreadLocalMap(ThreadLocalMap parentMap) {
Entry[] parentTable = parentMap.table;
int len = parentTable.length;
setThreshold(len);
table = new Entry[len];

// 遍历父线程的所有 Entry
for (int j = 0; j < len; j++) {
Entry e = parentTable[j];
if (e != null) {
@SuppressWarnings("unchecked")
ThreadLocal<Object> key = (ThreadLocal<Object>) e.get();
if (key != null) {
// 调用 childValue() 获取子线程应该继承的值
Object value = key.childValue(e.value);
Entry c = new Entry(key, value);
int h = key.threadLocalHashCode & (len - 1);
while (table[h] != null)
h = nextIndex(h, len);
table[h] = c;
size++;
}
}
}
}

为什么需要两个 Map?不能合并吗?

问题:为什么 Thread 要有 threadLocalsinheritableThreadLocals 两个 Map,不能合并成一个吗?

答案:不能合并,原因如下:

graph TB
    subgraph "如果只有一个 Map 的问题"
        direction TB
        
        subgraph "父线程"
            P_TL1["ThreadLocal A = 敏感数据"]
            P_TL2["ThreadLocal B = 普通数据"]
            P_ITL["InheritableThreadLocal C = 需要传递的数据"]
            style P_TL1 fill:#ffcdd2
            style P_TL2 fill:#fff9c4
            style P_ITL fill:#c8e6c9
        end
        
        arrow["创建子线程时<br/>如果只有一个 Map<br/>要么全部复制,要么全不复制"]
        style arrow fill:#ffebee
        
        subgraph "子线程"
            C_TL1["ThreadLocal A = 敏感数据 (不应该继承)"]
            C_TL2["ThreadLocal B = 普通数据 (不应该继承)"]
            C_ITL["InheritableThreadLocal C = 需要传递的数据 (应该继承)"]
            style C_TL1 fill:#ffcdd2
            style C_TL2 fill:#fff9c4
            style C_ITL fill:#c8e6c9
        end
    end

不能合并的四个核心原因

原因 说明
1. 语义隔离 普通 ThreadLocal 的设计初衷是线程隔离,不应该被子线程看到;InheritableThreadLocal 的设计初衷是父子传递。两者语义完全相反
2. 安全性 如果合并,敏感的 ThreadLocal 值(如数据库连接、事务状态)会意外被子线程继承,造成安全隐患
3. 性能优化 分开存储后,创建子线程时只需复制 inheritableThreadLocals,而不是全部 ThreadLocal 值,减少开销
4. 选择性继承 开发者可以明确选择哪些变量需要继承(使用 InheritableThreadLocal),哪些不需要(使用普通 ThreadLocal

继承流程对比

1
2
3
4
5
6
7
8
9
使用两个 Map(当前设计):
父线程
├─ threadLocals (不继承) ────────────────> 子线程 threadLocals (空)
└─ inheritableThreadLocals (继承) ──复制──> 子线程 inheritableThreadLocals

如果只有一个 Map(假设):
父线程
└─ threadLocals ──全部复制──> 子线程 threadLocals
↑ 问题:无法区分哪些应该继承,哪些不应该

InheritableThreadLocal 的浅拷贝问题

InheritableThreadLocal 在继承时存在浅拷贝问题childValue() 方法默认直接返回父线程的值引用,而不是深拷贝:

1
2
3
4
// InheritableThreadLocal 的默认实现
protected T childValue(T parentValue) {
return parentValue; // 直接返回引用,不是深拷贝!
}

问题演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
InheritableThreadLocal<List<String>> context = new InheritableThreadLocal<>();
List<String> list = new ArrayList<>();
list.add("parent-item");
context.set(list);

new Thread(() -> {
List<String> childList = context.get();
childList.add("child-item"); // 修改子线程的 list
System.out.println("子线程: " + childList);
}).start();

Thread.sleep(100);
System.out.println("父线程: " + context.get());
// 输出:
// 子线程: [parent-item, child-item]
// 父线程: [parent-item, child-item] -- 父线程的值也被修改了

解决方案:重写 childValue() 方法实现深拷贝:

1
2
3
4
5
6
7
InheritableThreadLocal<List<String>> context = new InheritableThreadLocal<>() {
@Override
protected List<String> childValue(List<String> parentValue) {
// 深拷贝:创建新的 ArrayList
return new ArrayList<>(parentValue);
}
};

InheritableThreadLocal 的局限性

InheritableThreadLocal 的局限性:它只在创建子线程时复制父线程的值。如果使用线程池,线程是复用的,不会每次都创建新线程,因此 InheritableThreadLocal 在线程池场景下无法正确传递上下文

这就是为什么需要 TransmittableThreadLocal——InheritableThreadLocal 对线程池极不友好,无法满足现代应用中大量使用线程池的场景。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 问题演示
InheritableThreadLocal<String> context = new InheritableThreadLocal<>();
ExecutorService executor = Executors.newFixedThreadPool(1);

// 第一次提交
context.set("request-1");
executor.submit(() -> {
System.out.println(context.get()); // 输出: request-1 (符合预期)
});

// 第二次提交(复用同一个线程)
context.set("request-2");
executor.submit(() -> {
System.out.println(context.get()); // 输出: request-1 (错误,期望是 request-2)
});

原因:线程池中的线程在第一次执行任务时就已经创建完成,此时继承了 request-1。后续任务复用这个线程时,不会再触发 Thread.init() 中的继承逻辑。

TransmittableThreadLocal:线程池场景的解决方案

阿里巴巴开源的 TransmittableThreadLocal (TTL) 解决了线程池场景下的上下文传递问题。

核心挑战:不能修改 Thread 类

InheritableThreadLocal 之所以能实现父子线程传递,是因为 JDK 对 Thread 类进行了专门改造——添加了 inheritableThreadLocals 字段和 init() 方法中的复制逻辑。

但对于第三方库(如 TTL),无法修改 JDK 的 Thread 类。那么如何在不修改 Thread 的情况下,实现线程池场景的上下文传递呢?

TTL 的巧妙解决方案:Capture-Replay-Restore

TTL 采用了一种完全不同的思路——在任务层面而非线程层面解决问题:

sequenceDiagram
    participant Main as 主线程
    participant TTL as TransmittableThreadLocal
    participant Task as TtlRunnable
    participant Pool as 线程池
    participant Worker as 工作线程
    
    Note over Main: 阶段1:设置上下文
    Main->>TTL: context.set("request-123")
    TTL->>Main: 存入 inheritableThreadLocals
    
    Note over Main,Task: 阶段2:提交任务时捕获(Capture)
    Main->>Task: TtlRunnable.get(runnable)
    Task->>Task: capture() 捕获所有 TTL 值
    Note over Task: 创建快照:{context: "request-123"}
    Main->>Pool: executor.submit(ttlRunnable)
    
    Note over Worker: 阶段3:执行前重放(Replay)
    Pool->>Worker: 分配任务给工作线程
    Worker->>Task: run()
    Task->>Task: backup = replay(captured)
    Note over Task: 1. 备份工作线程当前的 TTL 值<br/>2. 将快照中的值设置到工作线程<br/>3. 清理不在快照中的 TTL 变量
    
    Note over Worker: 阶段4:执行任务
    Task->>Worker: runnable.run()
    Worker->>TTL: context.get()
    TTL-->>Worker: 返回 "request-123" (正确)
    
    Note over Worker: 阶段5:执行后恢复(Restore)
    Task->>Task: restore(backup)
    Note over Task: 恢复工作线程原来的 TTL 值<br/>避免影响后续任务

核心实现原理

1. holder 注册机制

TTL 的关键创新是引入了一个全局注册表,记录所有 TTL 实例:

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
public class TransmittableThreadLocal<T> extends InheritableThreadLocal<T> {

// 全局注册表:记录当前线程所有设置过值的 TTL 实例
// 使用 InheritableThreadLocal 存储,确保子线程也能继承这个注册表
private static InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<?>, ?>> holder =
new InheritableThreadLocal<WeakHashMap<TransmittableThreadLocal<?>, ?>>() {
@Override
protected WeakHashMap<TransmittableThreadLocal<?>, ?> initialValue() {
return new WeakHashMap<>();
}

@Override
protected WeakHashMap<TransmittableThreadLocal<?>, ?> childValue(
WeakHashMap<TransmittableThreadLocal<?>, ?> parentValue) {
return new WeakHashMap<>(parentValue);
}
};

@Override
public final void set(T value) {
super.set(value);
if (value != null) {
// 设置值时,将当前 TTL 实例注册到 holder
holder.get().put(this, null);
} else {
holder.get().remove(this);
}
}
}

设计精妙之处

  • 使用 WeakHashMap 避免内存泄漏
  • holder 本身是 InheritableThreadLocal,确保子线程能继承注册表
  • 每次 set() 时自动注册,capture() 时遍历注册表获取所有 TTL 值

2. 拦截线程池的 execute 方法

TTL 解决线程池传递问题的核心是拦截线程池的 execute() 方法。无论是 TtlExecutors 包装还是 Java Agent,本质上都是在任务提交时进行拦截:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// TtlExecutors 的核心实现(简化版)
class ExecutorServiceTtlWrapper implements ExecutorService {
private final ExecutorService executorService;

@Override
public void execute(Runnable command) {
// 拦截 execute 方法,自动包装任务
executorService.execute(TtlRunnable.get(command));
}

@Override
public <T> Future<T> submit(Callable<T> task) {
// 拦截 submit 方法,自动包装任务
return executorService.submit(TtlCallable.get(task));
}
}

Java Agent 的字节码增强

1
2
3
4
5
6
7
// TtlAgent 在类加载时修改 ThreadPoolExecutor 的字节码
// 将 execute 方法的入口处插入以下逻辑:
public void execute(Runnable command) {
// 插入的代码:自动包装 Runnable
command = TtlRunnable.get(command, false, true);
// 原始的 execute 逻辑...
}

3. Worker 线程的 ThreadLocalMap Store/Restore

TTL 最关键的设计是处理工作线程原有 ThreadLocalMap 的保存和恢复。这是防止数据污染的核心机制:

sequenceDiagram
    participant Task as TtlRunnable
    participant Worker as 工作线程
    participant Map as ThreadLocalMap
    
    Note over Worker: 工作线程可能有自己的 TTL 值<br/>(来自之前执行的其他任务)
    
    Task->>Worker: run() 开始执行
    
    rect rgb(255, 245, 238)
        Note over Task,Map: Store 阶段
        Task->>Map: backup = 备份当前 ThreadLocalMap 中的 TTL 值
        Task->>Map: 清理不在 captured 中的 TTL 变量
        Task->>Map: 设置 captured 中的值到 ThreadLocalMap
    end
    
    Task->>Task: 执行原始任务 runnable.run()
    
    rect rgb(232, 245, 233)
        Note over Task,Map: Restore 阶段
        Task->>Map: 清理当前 ThreadLocalMap 中的 TTL 变量
        Task->>Map: 从 backup 恢复原来的 TTL 值
    end
    
    Note over Worker: 工作线程恢复到执行任务前的状态<br/>不影响后续任务

Store/Restore 的源码实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// TtlRunnable.run() 的核心逻辑
@Override
public void run() {
Object captured = capturedRef.get();

// ========== Store 阶段 ==========
// replay() 返回的是 backup(工作线程原有的值)
Object backup = Transmitter.replay(captured);

try {
// 执行原始任务
runnable.run();
} finally {
// ========== Restore 阶段 ==========
// 恢复工作线程原有的 ThreadLocalMap 状态
Transmitter.restore(backup);
}
}

为什么需要 Store/Restore?

场景 不做 Restore 的问题
任务 A 设置了 TTL 值 任务 B 复用同一个工作线程时,会读到任务 A 的值
工作线程有自己的 TTL 值 任务执行后,工作线程原有的值被覆盖,影响后续逻辑
任务执行中修改了 TTL 值 修改会"泄漏"到后续任务,造成数据污染

Store/Restore 确保

  1. 任务执行时只能看到提交任务时父线程传递的值
  2. 任务执行完毕后,工作线程恢复到执行任务前的状态
  3. 任务之间完全隔离,互不影响

4. Transmitter 工具类

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
public static class Transmitter {

// 捕获当前线程的所有 TTL 变量
public static Object capture() {
return new Snapshot(captureTtlValues(), captureThreadLocalValues());
}

private static WeakHashMap<TransmittableThreadLocal<Object>, Object> captureTtlValues() {
WeakHashMap<TransmittableThreadLocal<Object>, Object> ttl2Value =
new WeakHashMap<>();
// 遍历 holder 中所有注册的 TTL 实例
for (TransmittableThreadLocal<Object> threadLocal : holder.get().keySet()) {
// 获取值的副本
ttl2Value.put(threadLocal, threadLocal.copyValue());
}
return ttl2Value;
}

// 重放捕获的值到当前线程
public static Object replay(Object captured) {
Snapshot capturedSnapshot = (Snapshot) captured;
return new Snapshot(
replayTtlValues(capturedSnapshot.ttl2Value),
replayThreadLocalValues(capturedSnapshot.threadLocal2Value)
);
}

private static WeakHashMap<TransmittableThreadLocal<Object>, Object> replayTtlValues(
WeakHashMap<TransmittableThreadLocal<Object>, Object> captured) {

// 1. 备份当前线程(工作线程)的 TTL 值
WeakHashMap<TransmittableThreadLocal<Object>, Object> backup = new WeakHashMap<>();

for (Iterator<TransmittableThreadLocal<Object>> iterator =
holder.get().keySet().iterator(); iterator.hasNext(); ) {
TransmittableThreadLocal<Object> threadLocal = iterator.next();

// 备份当前值
backup.put(threadLocal, threadLocal.get());

// 2. 关键:清理不在 captured 中的 TTL 变量
// 防止线程复用导致的数据污染
if (!captured.containsKey(threadLocal)) {
iterator.remove();
threadLocal.superRemove();
}
}

// 3. 将捕获的值设置到当前线程
for (Map.Entry<TransmittableThreadLocal<Object>, Object> entry :
captured.entrySet()) {
entry.getKey().set(entry.getValue());
}

return backup;
}

// 恢复到重放前的状态
public static void restore(Object backup) {
Snapshot backupSnapshot = (Snapshot) backup;
restoreTtlValues(backupSnapshot.ttl2Value);
}
}

3. TtlRunnable 包装器

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
36
37
38
public final class TtlRunnable implements Runnable {
private final AtomicReference<Object> capturedRef;
private final Runnable runnable;
private final boolean releaseTtlValueReferenceAfterRun;

private TtlRunnable(Runnable runnable, boolean releaseTtlValueReferenceAfterRun) {
// 构造时捕获当前线程(父线程)的 TTL 变量
this.capturedRef = new AtomicReference<>(capture());
this.runnable = runnable;
this.releaseTtlValueReferenceAfterRun = releaseTtlValueReferenceAfterRun;
}

@Override
public void run() {
Object captured = capturedRef.get();
if (captured == null ||
(releaseTtlValueReferenceAfterRun &&
!capturedRef.compareAndSet(captured, null))) {
throw new IllegalStateException("TTL value reference is released!");
}

// 重放:将父线程捕获的值设置到当前线程(工作线程)
Object backup = replay(captured);
try {
// 执行原始任务
runnable.run();
} finally {
// 恢复:恢复工作线程的原始状态
restore(backup);
}
}

public static TtlRunnable get(Runnable runnable) {
if (runnable == null) return null;
if (runnable instanceof TtlRunnable) return (TtlRunnable) runnable;
return new TtlRunnable(runnable, false);
}
}

为什么需要清理不在快照中的 TTL 变量?

这是 TTL 设计中最精妙的部分。考虑以下场景:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
ExecutorService executor = Executors.newFixedThreadPool(1);

// 任务1:设置 TTL 值
context.set("task1-value");
executor.submit(TtlRunnable.get(() -> {
System.out.println(context.get()); // 输出: task1-value
context.set("modified-in-task1"); // 在任务中修改
}));

// 任务2:没有设置 TTL 值
// 如果不清理,任务2 会读到任务1 修改的值!
executor.submit(TtlRunnable.get(() -> {
System.out.println(context.get()); // 期望: null,而不是 "modified-in-task1"
}));

清理逻辑确保:任务执行时只能访问提交任务时父线程传递的值,而不是工作线程之前执行其他任务时遗留的值。

三种使用方式对比

方式 侵入性 实现原理 适用场景
TtlRunnable 包装 手动包装每个任务 少量任务需要传递上下文
TtlExecutors 包装 装饰器模式包装线程池 特定线程池需要传递上下文
Java Agent 字节码增强,自动包装 全局透明传递,推荐生产使用

方式一:修饰 Runnable/Callable

1
2
3
4
5
6
7
8
9
TransmittableThreadLocal<String> context = new TransmittableThreadLocal<>();
context.set("value-set-in-parent");

// 使用 TtlRunnable 包装
Runnable ttlRunnable = TtlRunnable.get(() -> {
System.out.println(context.get()); // 输出: value-set-in-parent
});

executorService.submit(ttlRunnable);

方式二:修饰线程池

1
2
3
4
5
6
7
8
ExecutorService executorService = Executors.newFixedThreadPool(10);
// 使用 TtlExecutors 包装线程池
executorService = TtlExecutors.getTtlExecutorService(executorService);

// 之后提交的任务自动具备 TTL 传递能力
executorService.submit(() -> {
System.out.println(context.get());
});

方式三:Java Agent 方式(推荐)

通过 Java Agent 在类加载时自动增强线程池,无需修改业务代码:

1
java -javaagent:transmittable-thread-local-x.x.x.jar -jar your-app.jar

Java Agent 的实现原理是在类加载时修改 ThreadPoolExecutorScheduledThreadPoolExecutorForkJoinPool 等类的字节码,自动将提交的 Runnable/Callable 包装为 TTL 版本。

TTL 核心数据结构图

graph TB
    subgraph "TTL 核心数据结构"
        direction TB
        
        subgraph Thread1["主线程 (提交任务)"]
            T1_TLM["threadLocals<br/>(ThreadLocalMap)"]
            T1_ITL["inheritableThreadLocals<br/>(ThreadLocalMap)"]
            T1_HOLDER["holder.get()<br/>(WeakHashMap)"]
            
            T1_TTL1["TTL1 → value1"]
            T1_TTL2["TTL2 → value2"]
            
            T1_TLM --> T1_TTL1
            T1_TLM --> T1_TTL2
            T1_HOLDER --> |"注册"| T1_TTL1
            T1_HOLDER --> |"注册"| T1_TTL2
        end
        
        subgraph Snapshot["捕获的快照 (Snapshot)"]
            S_MAP["WeakHashMap<br/>TTL → Value 副本"]
            S_TTL1["TTL1 → value1_copy"]
            S_TTL2["TTL2 → value2_copy"]
            S_MAP --> S_TTL1
            S_MAP --> S_TTL2
        end
        
        subgraph Thread2["工作线程 (执行任务)"]
            T2_TLM["threadLocals<br/>(ThreadLocalMap)"]
            T2_BACKUP["backup<br/>(原有值备份)"]
            T2_OLD["TTL3 → old_value"]
            T2_TLM --> T2_OLD
        end
        
        T1_HOLDER -->|"capture()"| Snapshot
        Snapshot -->|"replay()"| T2_TLM
        T2_OLD -->|"备份到"| T2_BACKUP
    end
    
    style T1_HOLDER fill:#e3f2fd
    style Snapshot fill:#fff3e0
    style T2_BACKUP fill:#e8f5e9

TTL 完整生命周期图

flowchart TB
    subgraph Phase1["阶段1: 主线程设置值"]
        P1_SET["context.set('request-123')"]
        P1_TLM["存入 threadLocals"]
        P1_REG["注册到 holder"]
        P1_SET --> P1_TLM
        P1_SET --> P1_REG
    end
    
    subgraph Phase2["阶段2: 提交任务时捕获"]
        P2_WRAP["TtlRunnable.get(runnable)"]
        P2_CAP["Transmitter.capture()"]
        P2_ITER["遍历 holder 中所有 TTL"]
        P2_COPY["复制每个 TTL 的值"]
        P2_SNAP["创建 Snapshot"]
        P2_WRAP --> P2_CAP
        P2_CAP --> P2_ITER
        P2_ITER --> P2_COPY
        P2_COPY --> P2_SNAP
    end
    
    subgraph Phase3["阶段3: 工作线程执行前"]
        P3_RUN["TtlRunnable.run()"]
        P3_REPLAY["Transmitter.replay(captured)"]
        P3_BACKUP["备份工作线程当前 TTL 值"]
        P3_CLEAN["清理不在快照中的 TTL"]
        P3_APPLY["应用快照中的值"]
        P3_RUN --> P3_REPLAY
        P3_REPLAY --> P3_BACKUP
        P3_BACKUP --> P3_CLEAN
        P3_CLEAN --> P3_APPLY
    end
    
    subgraph Phase4["阶段4: 执行业务逻辑"]
        P4_BIZ["runnable.run()"]
        P4_GET["context.get()"]
        P4_VAL["返回 'request-123' (正确)"]
        P4_BIZ --> P4_GET
        P4_GET --> P4_VAL
    end
    
    subgraph Phase5["阶段5: 执行后恢复"]
        P5_RESTORE["Transmitter.restore(backup)"]
        P5_CLEAR["清理当前 TTL 值"]
        P5_RECOVER["从 backup 恢复原有值"]
        P5_DONE["工作线程状态恢复"]
        P5_RESTORE --> P5_CLEAR
        P5_CLEAR --> P5_RECOVER
        P5_RECOVER --> P5_DONE
    end
    
    Phase1 --> Phase2
    Phase2 --> Phase3
    Phase3 --> Phase4
    Phase4 --> Phase5
    
    style Phase1 fill:#e8f5e9
    style Phase2 fill:#fff3e0
    style Phase3 fill:#e3f2fd
    style Phase4 fill:#f3e5f5
    style Phase5 fill:#ffebee

TTL 多任务隔离机制图

sequenceDiagram
    participant Main as 主线程
    participant Pool as 线程池
    participant W1 as Worker-1
    
    Note over Main,W1: 场景:两个任务复用同一个工作线程
    
    rect rgb(232, 245, 233)
        Note over Main: 任务1:设置 context = "task1"
        Main->>Main: context.set("task1")
        Main->>Pool: submit(TtlRunnable(task1))
        Note over Pool: 快照1: {context: "task1"}
    end
    
    rect rgb(255, 243, 224)
        Pool->>W1: 分配任务1
        W1->>W1: backup = replay(快照1)
        Note over W1: backup = {} (工作线程原本为空)
        W1->>W1: 设置 context = "task1"
        W1->>W1: 执行 task1
        W1->>W1: context.set("modified") 任务中修改
        W1->>W1: restore(backup)
        Note over W1: 清理 context,恢复为空
    end
    
    rect rgb(227, 242, 253)
        Note over Main: 任务2:设置 context = "task2"
        Main->>Main: context.set("task2")
        Main->>Pool: submit(TtlRunnable(task2))
        Note over Pool: 快照2: {context: "task2"}
    end
    
    rect rgb(243, 229, 245)
        Pool->>W1: 分配任务2 (复用同一线程)
        W1->>W1: backup = replay(快照2)
        Note over W1: backup = {} (已被恢复为空)
        W1->>W1: 设置 context = "task2"
        W1->>W1: 执行 task2
        W1->>W1: context.get() 返回 "task2" (正确)
        Note over W1: 不会读到 "modified"!
        W1->>W1: restore(backup)
    end

TTL 三种使用方式对比图

flowchart LR
    subgraph Way1["方式1: 包装 Runnable"]
        W1_CODE["TtlRunnable.get(runnable)"]
        W1_PROS["优点: 精确控制"]
        W1_CONS["缺点: 侵入性高"]
        W1_CODE --> W1_PROS
        W1_CODE --> W1_CONS
    end
    
    subgraph Way2["方式2: 包装线程池"]
        W2_CODE["TtlExecutors.getTtlExecutorService(executor)"]
        W2_PROS["优点: 一次包装"]
        W2_CONS["缺点: 需修改代码"]
        W2_CODE --> W2_PROS
        W2_CODE --> W2_CONS
    end
    
    subgraph Way3["方式3: Java Agent"]
        W3_CODE["-javaagent:ttl.jar"]
        W3_PROS["优点: 零侵入"]
        W3_CONS["缺点: 需要 JVM 参数"]
        W3_CODE --> W3_PROS
        W3_CODE --> W3_CONS
    end
    
    Way1 -->|"更便捷"| Way2
    Way2 -->|"更透明"| Way3
    
    style Way1 fill:#ffebee
    style Way2 fill:#fff3e0
    style Way3 fill:#e8f5e9

holder 注册机制详解图

graph TB
    subgraph TTL_Instance["TTL 实例"]
        TTL1["TTL1: traceId"]
        TTL2["TTL2: userId"]
        TTL3["TTL3: tenantId"]
    end
    
    subgraph SetOps["set 操作"]
        SET1["ttl1.set(trace-001)"]
        SET2["ttl2.set(user-123)"]
        SET3["ttl3.set(tenant-A)"]
    end
    
    subgraph HolderMap["holder WeakHashMap"]
        REG1["TTL1 -> null"]
        REG2["TTL2 -> null"]
        REG3["TTL3 -> null"]
    end
    
    subgraph CaptureFlow["capture 遍历"]
        CAP1["遍历 holder.keySet"]
        CAP2["对每个TTL调用get"]
        CAP3["生成快照Map"]
        CAP1 --> CAP2
        CAP2 --> CAP3
    end
    
    SET1 --> TTL1
    SET1 --> REG1
    SET2 --> TTL2
    SET2 --> REG2
    SET3 --> TTL3
    SET3 --> REG3
    
    HolderMap --> CAP1
    
    style HolderMap fill:#e3f2fd
    style CaptureFlow fill:#fff3e0

InheritableThreadLocal vs TransmittableThreadLocal

graph TB
    subgraph "InheritableThreadLocal(JDK 内置)"
        direction TB
        ITL_HOW["实现方式:修改 Thread 类"]
        ITL_WHEN["传递时机:创建子线程时"]
        ITL_LIMIT["局限性:线程池场景失效"]
        style ITL_LIMIT fill:#ffcdd2
    end
    
    subgraph "TransmittableThreadLocal(阿里开源)"
        direction TB
        TTL_HOW["实现方式:包装任务/线程池"]
        TTL_WHEN["传递时机:提交任务时"]
        TTL_ADVANTAGE["优势:完美支持线程池"]
        style TTL_ADVANTAGE fill:#c8e6c9
    end
    
    subgraph "设计对比"
        ITL_DESIGN["Thread 持有 Map<br/>在 init() 中复制"]
        TTL_DESIGN["holder 注册所有 TTL<br/>capture/replay/restore"]
    end

适用场景

  1. 分布式追踪:TraceId、SpanId 的跨线程传递
  2. 日志上下文:MDC(Mapped Diagnostic Context)的传递
  3. 用户上下文:用户身份信息、租户信息的传递
  4. 事务上下文:分布式事务的上下文传递

ThreadLocal 核心源码深度解析

ThreadLocal.set() 源码详解

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
/**
* 设置当前线程的线程局部变量的值
*
* 核心流程:
* 1. 获取当前线程
* 2. 获取当前线程的 ThreadLocalMap
* 3. 如果 Map 存在,直接设置值
* 4. 如果 Map 不存在,创建 Map 并设置初始值
*
* @param value 要存储的值
*/
public void set(T value) {
// 获取当前执行线程
Thread t = Thread.currentThread();

// 获取当前线程的 ThreadLocalMap
// 注意:这里是获取线程的 threadLocals 字段,不是 inheritableThreadLocals
ThreadLocalMap map = getMap(t);

if (map != null) {
// Map 已存在,直接设置
// key 是当前 ThreadLocal 实例(this),value 是用户传入的值
map.set(this, value);
} else {
// Map 不存在,创建新的 ThreadLocalMap
// 并以当前 ThreadLocal 为 key,value 为值创建第一个 Entry
createMap(t, value);
}
}

/**
* 获取线程的 ThreadLocalMap
*
* 设计要点:
* - Thread 类有一个 threadLocals 字段,类型是 ThreadLocal.ThreadLocalMap
* - 这个字段是包级私有的,只有 ThreadLocal 能访问
* - 这就是"Thread 持有 Map"设计的体现
*/
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 直接访问 Thread 的 threadLocals 字段
}

/**
* 为线程创建 ThreadLocalMap
*
* 设计要点:
* - 延迟初始化:只有第一次 set 时才创建 Map
* - 节省内存:不使用 ThreadLocal 的线程不会有 Map
*/
void createMap(Thread t, T firstValue) {
// 创建新的 ThreadLocalMap,并设置到线程的 threadLocals 字段
t.threadLocals = new ThreadLocalMap(this, firstValue);
}

ThreadLocal.get() 源码详解

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
/**
* 获取当前线程的线程局部变量的值
*
* 核心流程:
* 1. 获取当前线程的 ThreadLocalMap
* 2. 如果 Map 存在且包含当前 ThreadLocal 的 Entry,返回 value
* 3. 否则调用 setInitialValue() 设置并返回初始值
*
* @return 当前线程的线程局部变量值
*/
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);

if (map != null) {
// 以当前 ThreadLocal 实例为 key 查找 Entry
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T) e.value;
return result;
}
}

// Map 不存在或 Entry 不存在,设置初始值
return setInitialValue();
}

/**
* 设置初始值的变体方法
*
* 设计要点:
* - 与 set() 类似,但调用 initialValue() 获取初始值
* - initialValue() 默认返回 null,可被子类重写
* - 这是模板方法模式的应用
*/
private T setInitialValue() {
// 调用 initialValue() 获取初始值(默认返回 null)
T value = initialValue();

Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);

if (map != null) {
map.set(this, value);
} else {
createMap(t, value);
}

// 如果是 TerminatingThreadLocal,注册到终止回调列表
if (this instanceof TerminatingThreadLocal) {
TerminatingThreadLocal.register((TerminatingThreadLocal<?>) this);
}

return value;
}

/**
* 返回此线程局部变量的初始值
*
* 设计要点:
* - 这是一个受保护的方法,子类可以重写
* - 默认返回 null
* - 典型用法是使用 ThreadLocal.withInitial() 工厂方法
*/
protected T initialValue() {
return null;
}

ThreadLocalMap.set() 源码详解

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/**
* ThreadLocalMap 的 set 方法
*
* 核心算法:开放地址法(线性探测)
*
* 流程:
* 1. 计算 hash 槽位
* 2. 线性探测找到合适的位置
* 3. 处理三种情况:找到相同 key、找到 stale entry、找到空槽
*/
private void set(ThreadLocal<?> key, Object value) {
Entry[] tab = table;
int len = tab.length;

// 计算初始槽位:使用 ThreadLocal 的 threadLocalHashCode
// threadLocalHashCode 使用黄金分割数 0x61c88647 递增
// 这个魔数能让 hash 分布更均匀
int i = key.threadLocalHashCode & (len - 1);

// 线性探测:从 i 开始向后查找
for (Entry e = tab[i]; e != null; e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); // 获取弱引用指向的 ThreadLocal

// 情况1:找到相同的 key,直接更新 value
if (k == key) {
e.value = value;
return;
}

// 情况2:key 为 null,说明是 Stale Entry
// 这是弱引用被 GC 回收后的状态
if (k == null) {
// 替换 stale entry,同时清理其他 stale entries
replaceStaleEntry(key, value, i);
return;
}

// 情况3:key 不同且不为 null,继续探测下一个槽位
}

// 找到空槽,创建新 Entry
tab[i] = new Entry(key, value);
int sz = ++size;

// 尝试清理一些 stale entries
// 如果没有清理任何 entry 且 size 超过阈值,则扩容
if (!cleanSomeSlots(i, sz) && sz >= threshold) {
rehash();
}
}

/**
* 计算下一个槽位索引(环形数组)
*/
private static int nextIndex(int i, int len) {
return ((i + 1 < len) ? i + 1 : 0);
}

ThreadLocalMap.getEntry() 源码详解

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 根据 ThreadLocal key 获取 Entry
*
* 设计要点:
* - 快速路径:直接命中
* - 慢速路径:线性探测 + 清理 stale entries
*/
private Entry getEntry(ThreadLocal<?> key) {
// 计算槽位
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];

// 快速路径:直接命中
if (e != null && e.get() == key) {
return e;
} else {
// 慢速路径:需要线性探测
return getEntryAfterMiss(key, i, e);
}
}

/**
* 未直接命中时的查找逻辑
*
* 设计要点:
* - 线性探测查找目标 Entry
* - 遇到 stale entry 时触发清理
*/
private Entry getEntryAfterMiss(ThreadLocal<?> key, int i, Entry e) {
Entry[] tab = table;
int len = tab.length;

while (e != null) {
ThreadLocal<?> k = e.get();

// 找到目标
if (k == key) {
return e;
}

// 遇到 stale entry,触发清理
if (k == null) {
expungeStaleEntry(i); // 清理 stale entry
} else {
i = nextIndex(i, len); // 继续探测
}

e = tab[i];
}

return null; // 未找到
}

expungeStaleEntry() 核心清理逻辑

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/**
* 清理 stale entry 的核心方法
*
* 这是 ThreadLocalMap 自动清理机制的核心
*
* 流程:
* 1. 清理指定位置的 stale entry
* 2. 向后扫描,清理更多 stale entries
* 3. 对非 stale entries 进行 rehash(重新定位)
*
* @param staleSlot 已知的 stale entry 位置
* @return 扫描结束的位置(第一个 null 槽位)
*/
private int expungeStaleEntry(int staleSlot) {
Entry[] tab = table;
int len = tab.length;

// ========== 步骤1:清理当前 stale entry ==========
tab[staleSlot].value = null; // 断开 value 的强引用,使其可被 GC
tab[staleSlot] = null; // 清空槽位
size--;

// ========== 步骤2:向后扫描并处理 ==========
Entry e;
int i;
for (i = nextIndex(staleSlot, len); (e = tab[i]) != null; i = nextIndex(i, len)) {
ThreadLocal<?> k = e.get();

if (k == null) {
// 又发现一个 stale entry,清理它
e.value = null;
tab[i] = null;
size--;
} else {
// 非 stale entry,需要 rehash
// 因为前面的槽位可能已经空出来了
int h = k.threadLocalHashCode & (len - 1);
if (h != i) {
// 当前位置不是理想位置,需要重新定位
tab[i] = null;

// 从理想位置开始,找到第一个空槽
while (tab[h] != null) {
h = nextIndex(h, len);
}
tab[h] = e;
}
}
}

return i; // 返回第一个 null 槽位的索引
}

基于 ThreadLocal 的设计模式框架实例

实例1:Spring 的 RequestContextHolder

Spring 框架使用 ThreadLocal 实现请求上下文的线程隔离:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
/**
* Spring RequestContextHolder 源码解析
*
* 设计模式:Holder 模式 + 策略模式
*
* 核心思想:
* - 使用 ThreadLocal 存储当前请求的 RequestAttributes
* - 支持普通 ThreadLocal 和 InheritableThreadLocal 两种模式
* - 提供静态方法访问当前请求上下文
*/
public abstract class RequestContextHolder {

// 普通 ThreadLocal:不支持子线程继承
private static final ThreadLocal<RequestAttributes> requestAttributesHolder =
new NamedThreadLocal<>("Request attributes");

// InheritableThreadLocal:支持子线程继承
private static final ThreadLocal<RequestAttributes> inheritableRequestAttributesHolder =
new NamedInheritableThreadLocal<>("Request context");

/**
* 重置当前线程的 RequestAttributes
*/
public static void resetRequestAttributes() {
requestAttributesHolder.remove();
inheritableRequestAttributesHolder.remove();
}

/**
* 设置当前线程的 RequestAttributes
*
* @param attributes 请求属性
* @param inheritable 是否可被子线程继承
*/
public static void setRequestAttributes(RequestAttributes attributes,
boolean inheritable) {
if (attributes == null) {
resetRequestAttributes();
} else {
if (inheritable) {
inheritableRequestAttributesHolder.set(attributes);
requestAttributesHolder.remove();
} else {
requestAttributesHolder.set(attributes);
inheritableRequestAttributesHolder.remove();
}
}
}

/**
* 获取当前线程的 RequestAttributes
*/
public static RequestAttributes getRequestAttributes() {
RequestAttributes attributes = requestAttributesHolder.get();
if (attributes == null) {
attributes = inheritableRequestAttributesHolder.get();
}
return attributes;
}

/**
* 获取当前请求(必须存在)
*/
public static RequestAttributes currentRequestAttributes()
throws IllegalStateException {
RequestAttributes attributes = getRequestAttributes();
if (attributes == null) {
throw new IllegalStateException(
"No thread-bound request found: ...");
}
return attributes;
}
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// 在 Filter 或 Interceptor 中设置
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
RequestContextHolder.setRequestAttributes(
new ServletRequestAttributes(request, response));
return true;
}

// 在业务代码中获取
public void businessMethod() {
HttpServletRequest request = ((ServletRequestAttributes)
RequestContextHolder.currentRequestAttributes()).getRequest();
String userId = request.getHeader("X-User-Id");
}

// 在 Filter 或 Interceptor 中清理
@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
RequestContextHolder.resetRequestAttributes();
}

实例2:SLF4J 的 MDC(Mapped Diagnostic Context)

SLF4J 的 MDC 使用 ThreadLocal 实现日志上下文的线程隔离:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
/**
* MDC 核心实现(基于 Logback)
*
* 设计模式:门面模式 + ThreadLocal
*
* 核心思想:
* - 使用 ThreadLocal<Map<String, String>> 存储诊断上下文
* - 日志输出时自动附加 MDC 中的信息
* - 支持嵌套上下文(通过 Map 的 copy)
*/
public class LogbackMDCAdapter implements MDCAdapter {

// 使用 InheritableThreadLocal 存储 MDC Map
// 子线程可以继承父线程的 MDC 上下文
final ThreadLocal<Map<String, String>> copyOnInheritThreadLocal =
new InheritableThreadLocal<Map<String, String>>() {
@Override
protected Map<String, String> childValue(
Map<String, String> parentValue) {
if (parentValue == null) {
return null;
}
// 深拷贝,避免父子线程共享同一个 Map
return new HashMap<>(parentValue);
}
};

/**
* 设置 MDC 键值对
*/
public void put(String key, String val) {
if (key == null) {
throw new IllegalArgumentException("key cannot be null");
}
Map<String, String> map = copyOnInheritThreadLocal.get();
if (map == null) {
map = new HashMap<>();
copyOnInheritThreadLocal.set(map);
}
map.put(key, val);
}

/**
* 获取 MDC 值
*/
public String get(String key) {
Map<String, String> map = copyOnInheritThreadLocal.get();
if (map != null && key != null) {
return map.get(key);
}
return null;
}

/**
* 移除 MDC 键值对
*/
public void remove(String key) {
Map<String, String> map = copyOnInheritThreadLocal.get();
if (map != null) {
map.remove(key);
}
}

/**
* 清空 MDC
*/
public void clear() {
Map<String, String> map = copyOnInheritThreadLocal.get();
if (map != null) {
map.clear();
copyOnInheritThreadLocal.remove();
}
}
}

使用示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 在请求入口设置 TraceId
public class TraceIdFilter implements Filter {
@Override
public void doFilter(ServletRequest request,
ServletResponse response,
FilterChain chain) {
try {
String traceId = UUID.randomUUID().toString();
MDC.put("traceId", traceId);
MDC.put("userId", getUserId(request));

chain.doFilter(request, response);
} finally {
MDC.clear(); // 必须清理!
}
}
}

// logback.xml 配置
// <pattern>%d{HH:mm:ss} [%X{traceId}] [%X{userId}] %-5level %logger - %msg%n</pattern>

// 日志输出示例
// 14:30:25 [abc-123-def] [user001] INFO c.e.UserService - 用户登录成功

实例3:MyBatis 的 SqlSession 管理

MyBatis-Spring 使用 ThreadLocal 管理 SqlSession 的生命周期:

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
/**
* SqlSessionHolder 和 TransactionSynchronizationManager
*
* 设计模式:资源绑定模式
*
* 核心思想:
* - 将 SqlSession 绑定到当前线程
* - 同一事务内复用同一个 SqlSession
* - 事务结束时自动清理
*/
public abstract class TransactionSynchronizationManager {

// 存储线程绑定的资源(如 SqlSession、Connection 等)
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");

// 存储事务同步回调
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");

// 当前事务名称
private static final ThreadLocal<String> currentTransactionName =
new NamedThreadLocal<>("Current transaction name");

// 当前事务是否只读
private static final ThreadLocal<Boolean> currentTransactionReadOnly =
new NamedThreadLocal<>("Current transaction read-only status");

// 当前事务隔离级别
private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
new NamedThreadLocal<>("Current transaction isolation level");

// 是否有实际的事务激活
private static final ThreadLocal<Boolean> actualTransactionActive =
new NamedThreadLocal<>("Actual transaction active");

/**
* 绑定资源到当前线程
*/
public static void bindResource(Object key, Object value) {
Map<Object, Object> map = resources.get();
if (map == null) {
map = new HashMap<>();
resources.set(map);
}
Object oldValue = map.put(key, value);
if (oldValue != null) {
throw new IllegalStateException(
"Already value [" + oldValue + "] for key [" + key + "] bound to thread");
}
}

/**
* 获取绑定到当前线程的资源
*/
public static Object getResource(Object key) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
return map.get(key);
}

/**
* 解绑资源
*/
public static Object unbindResource(Object key) {
Map<Object, Object> map = resources.get();
if (map == null) {
return null;
}
Object value = map.remove(key);
if (map.isEmpty()) {
resources.remove();
}
return value;
}
}

实例4:自定义租户上下文(多租户架构)

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
/**
* 多租户上下文管理器
*
* 设计模式:上下文对象模式 + ThreadLocal
*
* 适用场景:SaaS 多租户系统
*/
public class TenantContext {

private static final ThreadLocal<TenantInfo> TENANT_HOLDER =
new ThreadLocal<>();

// 使用 TTL 支持线程池场景
private static final TransmittableThreadLocal<TenantInfo> TTL_TENANT_HOLDER =
new TransmittableThreadLocal<>();

/**
* 租户信息
*/
public static class TenantInfo {
private final String tenantId;
private final String tenantName;
private final String dataSourceKey;
private final Map<String, Object> attributes;

public TenantInfo(String tenantId, String tenantName, String dataSourceKey) {
this.tenantId = tenantId;
this.tenantName = tenantName;
this.dataSourceKey = dataSourceKey;
this.attributes = new HashMap<>();
}

// getters...
public String getTenantId() { return tenantId; }
public String getTenantName() { return tenantName; }
public String getDataSourceKey() { return dataSourceKey; }
public Object getAttribute(String key) { return attributes.get(key); }
public void setAttribute(String key, Object value) { attributes.put(key, value); }
}

/**
* 设置当前租户(支持线程池)
*/
public static void setTenant(TenantInfo tenant) {
TTL_TENANT_HOLDER.set(tenant);
}

/**
* 获取当前租户
*/
public static TenantInfo getTenant() {
return TTL_TENANT_HOLDER.get();
}

/**
* 获取当前租户 ID
*/
public static String getTenantId() {
TenantInfo tenant = getTenant();
return tenant != null ? tenant.getTenantId() : null;
}

/**
* 清除租户上下文
*/
public static void clear() {
TTL_TENANT_HOLDER.remove();
}

/**
* 在指定租户上下文中执行操作
*/
public static <T> T executeWithTenant(TenantInfo tenant, Supplier<T> action) {
TenantInfo previous = getTenant();
try {
setTenant(tenant);
return action.get();
} finally {
if (previous != null) {
setTenant(previous);
} else {
clear();
}
}
}

/**
* 在指定租户上下文中执行操作(无返回值)
*/
public static void runWithTenant(TenantInfo tenant, Runnable action) {
executeWithTenant(tenant, () -> {
action.run();
return null;
});
}
}

/**
* 租户拦截器
*/
public class TenantInterceptor implements HandlerInterceptor {

@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
Object handler) {
String tenantId = request.getHeader("X-Tenant-Id");
if (tenantId != null) {
TenantInfo tenant = loadTenantInfo(tenantId);
TenantContext.setTenant(tenant);
}
return true;
}

@Override
public void afterCompletion(HttpServletRequest request,
HttpServletResponse response,
Object handler, Exception ex) {
TenantContext.clear(); // 必须清理!
}

private TenantInfo loadTenantInfo(String tenantId) {
// 从缓存或数据库加载租户信息
return new TenantInfo(tenantId, "租户名称", "ds_" + tenantId);
}
}

/**
* 动态数据源路由
*/
public class TenantDataSourceRouter extends AbstractRoutingDataSource {

@Override
protected Object determineCurrentLookupKey() {
// 根据当前租户选择数据源
return TenantContext.getTenant() != null
? TenantContext.getTenant().getDataSourceKey()
: "default";
}
}

实例5:安全上下文(类似 Spring Security)

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
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
/**
* 安全上下文管理器
*
* 设计模式:策略模式 + ThreadLocal
*
* 参考 Spring Security 的 SecurityContextHolder 设计
*/
public class SecurityContextHolder {

// 上下文存储策略
public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
public static final String MODE_GLOBAL = "MODE_GLOBAL";

private static String strategyName = MODE_THREADLOCAL;
private static SecurityContextHolderStrategy strategy;

static {
initialize();
}

private static void initialize() {
switch (strategyName) {
case MODE_THREADLOCAL:
strategy = new ThreadLocalSecurityContextHolderStrategy();
break;
case MODE_INHERITABLETHREADLOCAL:
strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
break;
case MODE_GLOBAL:
strategy = new GlobalSecurityContextHolderStrategy();
break;
default:
throw new IllegalArgumentException("Unknown strategy: " + strategyName);
}
}

public static void setContext(SecurityContext context) {
strategy.setContext(context);
}

public static SecurityContext getContext() {
return strategy.getContext();
}

public static void clearContext() {
strategy.clearContext();
}

public static SecurityContext createEmptyContext() {
return strategy.createEmptyContext();
}
}

/**
* 策略接口
*/
interface SecurityContextHolderStrategy {
void clearContext();
SecurityContext getContext();
void setContext(SecurityContext context);
SecurityContext createEmptyContext();
}

/**
* ThreadLocal 策略实现
*/
class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy {

private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<>();

@Override
public void clearContext() {
contextHolder.remove();
}

@Override
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}

@Override
public void setContext(SecurityContext context) {
if (context == null) {
throw new IllegalArgumentException("Context cannot be null");
}
contextHolder.set(context);
}

@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}

/**
* InheritableThreadLocal 策略实现
*/
class InheritableThreadLocalSecurityContextHolderStrategy
implements SecurityContextHolderStrategy {

private static final ThreadLocal<SecurityContext> contextHolder =
new InheritableThreadLocal<>();

// 实现与 ThreadLocalSecurityContextHolderStrategy 类似...
@Override
public void clearContext() {
contextHolder.remove();
}

@Override
public SecurityContext getContext() {
SecurityContext ctx = contextHolder.get();
if (ctx == null) {
ctx = createEmptyContext();
contextHolder.set(ctx);
}
return ctx;
}

@Override
public void setContext(SecurityContext context) {
contextHolder.set(context);
}

@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}

/**
* 安全上下文
*/
interface SecurityContext {
Authentication getAuthentication();
void setAuthentication(Authentication authentication);
}

class SecurityContextImpl implements SecurityContext {
private Authentication authentication;

@Override
public Authentication getAuthentication() {
return authentication;
}

@Override
public void setAuthentication(Authentication authentication) {
this.authentication = authentication;
}
}

interface Authentication {
String getPrincipal();
Collection<String> getAuthorities();
boolean isAuthenticated();
}

设计模式总结

graph TB
    subgraph "基于 ThreadLocal 的设计模式"
        direction TB
        
        subgraph Pattern1["Holder 模式"]
            H1["静态 ThreadLocal 变量"]
            H2["静态 get/set/remove 方法"]
            H3["示例: RequestContextHolder"]
        end
        
        subgraph Pattern2["上下文对象模式"]
            C1["封装多个相关属性"]
            C2["提供便捷访问方法"]
            C3["示例: TenantContext"]
        end
        
        subgraph Pattern3["策略模式"]
            S1["多种存储策略"]
            S2["运行时可切换"]
            S3["示例: SecurityContextHolder"]
        end
        
        subgraph Pattern4["资源绑定模式"]
            R1["绑定资源到线程"]
            R2["事务内复用"]
            R3["示例: TransactionSynchronizationManager"]
        end
        
        subgraph Pattern5["诊断上下文模式"]
            D1["键值对存储"]
            D2["日志自动附加"]
            D3["示例: MDC"]
        end
    end
    
    style Pattern1 fill:#e3f2fd
    style Pattern2 fill:#e8f5e9
    style Pattern3 fill:#fff3e0
    style Pattern4 fill:#f3e5f5
    style Pattern5 fill:#ffebee

最佳实践

如何正确使用 ThreadLocal

ThreadLocal 最好的用法是做一个 request scope 的缓存——在请求开始时设置,请求结束时清理。在线程里长期复用 ThreadLocal 其实极度危险。

正确的使用模式

1
2
3
4
5
6
7
8
9
10
private static final ThreadLocal<SomeObject> threadLocal = new ThreadLocal<>();

public void doSomething() {
try {
threadLocal.set(new SomeObject());
// 业务逻辑
} finally {
threadLocal.remove(); // 确保清理,避免内存泄漏
}
}

最佳实践清单

  1. 手动清理:使用完 ThreadLocal 后立即调用 remove(),这是最可靠的方式
  2. 声明为 static:ThreadLocal 变量应该声明为 static,避免每个对象实例都创建新的 ThreadLocal
  3. 避免静态变量泄漏:谨慎管理 ThreadLocal 静态变量的生命周期
  4. 线程池场景特别注意:在使用线程池时,线程不会被销毁,必须手动清理
  5. 使用 try-finally:确保在 finally 块中调用 remove(),即使发生异常也能清理

WeakHashMap 与 ThreadLocalMap 的设计对比

WeakHashMap 和 ThreadLocalMap 有相似的设计理念——利用弱引用实现自动清理。

WeakHashMap 利用下一次操作来触发 clear,好像有一个后台线程来维护 Map 一样。这种"惰性清理"的设计模式在 ThreadLocalMap 中也有体现:只有在 get/set/remove 操作时才会触发 Stale Entry 的清理。

这种设计的优点是避免了额外的清理线程开销,缺点是如果长时间没有操作,过期数据不会被及时清理。

总结

ThreadLocal 是 Java 并发编程中实现线程封闭的核心工具,其设计体现了几个重要的工程智慧:

  1. 反转持有关系:让 Thread 持有 Map,而不是让 Map 持有 Thread,从根本上避免了线程无法回收的问题
  2. 弱引用 + 惰性清理:通过弱引用 key 和惰性清理机制,在不影响性能的前提下尽可能避免内存泄漏
  3. 开放地址法:针对 ThreadLocal 的使用特点(Entry 数量少、需要顺便清理 Stale Entry),选择了更合适的哈希冲突解决方案

在实际使用中,要牢记:

  • ThreadLocal 应该声明为 static
  • 使用完毕后必须调用 remove()
  • 线程池场景下考虑使用 TransmittableThreadLocal