ThreadLocal 提供執行緒局部變量,實現「同一代碼、不同執行緒、隔離數據副本」。本文深入 JDK 源碼 (Thread、ThreadLocal、ThreadLocalMap、Entry),揭示獲取/設置/移除全流程、哈希定址、擴容清理、記憶體洩漏根因與解決方案。
Thread └── threadLocals: ThreadLocalMap (ThreadLocal → Entry → value) └── inheritableThreadLocals: ThreadLocalMap (繼承性 ThreadLocal) ThreadLocal └── threadLocalHashCode: int (原子生成,間隔 0x61C88647) └── initialValue(): T (延遲初始化鉤子) ThreadLocalMap └── Entry[] table: 弱引用鍵 (ThreadLocal) + 強引用值 └── size, threshold: 擴容閾值 (負載因子 2/3) └── set/get/remove/rehash/expungeStaleEntries
public T get() {
Thread t = Thread.currentThread(); // 1. 獲取當前執行緒
ThreadLocalMap map = getMap(t); // 2. 取得執行緒專屬 Map
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); // 3. 以 this (ThreadLocal) 為鍵查找
if (e != null) return (T) e.value; // 4. 命中直接返回值
}
return setInitialValue(); // 5. 未命中 → 初始化
}
ThreadLocalMap getMap(Thread t) {
return t.threadLocals; // 直接訪問 Thread 字段
}
private T setInitialValue() {
T value = initialValue(); // 6. 調用初始值 (預設 null,可覆寫)
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) map.set(this, value); // 7. Map 存在 → 寫入
else createMap(t, value); // 8. Map 不存在 → 建立 Map + 寫入
return value;
}
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
static class Entry extends WeakReference> { Object value; // 強引用值! Entry(ThreadLocal> k, Object v) { super(k); value = v; } }
// ThreadLocal 構造時原子生成 hashCode
private final int threadLocalHashCode = nextHashCode();
private static AtomicInteger nextHashCode = new AtomicInteger();
private static final int HASH_INCREMENT = 0x61C88647; // 斐波那契數/黃金分割,分佈均勻
// Map 存取索引計算
int i = key.threadLocalHashCode & (table.length - 1); // 位與取模 (長度必為 2^n)
// 衝突解決:線性探測
private Entry getEntry(ThreadLocal> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key) return e; // 命中
return getEntryAfterMiss(key, i, e); // 未命中 → 向後探測
}
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) map.set(this, value);
else createMap(t, value);
}
public void remove() {
ThreadLocalMap m = getMap(Thread.currentThread());
if (m != null) m.remove(this); // 清理 Entry,斷開 value 強引用
}
// Map.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); // 清理該槽位及後續過期槽位
return;
}
}
}
Entry.value **強引用** 仍指向大對象 → **無法被 GC**。Thread → ThreadLocalMap → Entry (key=null) → value (強引用) → 大對象。| 機制 | 觸發時機 | 作用 |
|---|---|---|
expungeStaleEntry | get()/set()/remove() 遇到 key==null | 掃描並清理連續過期槽位,斷開 value 引用 |
cleanSomeSlots | get() 時機率性 (log2(n) 概率) | 掃描部分槽位清理過期 Entry |
rehash | 擴容時 | 全量掃描清理所有過期 Entry |
ThreadLocal 設置後**永不再讀寫**,且執行緒長壽命 → 洩漏必發。InheritableThreadLocal 子執行緒複製父值,同樣受此問題影響。// ✅ 正確:ThreadLocal 本身強引用常駐,不會被 GC private static final ThreadLocalTRACE_ID = ThreadLocal.withInitial(() -> System.nanoTime()); // ❌ 錯誤:局部變量引用,方法結束後 ThreadLocal 成弱可達 void bad() { ThreadLocal tl = new ThreadLocal<>(); // 方法結束即可被 GC tl.set("value"); }
public void process(Request req) {
TRACE_ID.set(generateTraceId());
try {
// 業務邏輯
} finally {
TRACE_ID.remove(); // 關鍵!斷開 value 引用,觸發清理
}
}
public class ThreadLocalWrapper implements Runnable {
private final Runnable task;
private final Map, Object> snapshots; // 可選:保存父上下文
public ThreadLocalWrapper(Runnable task) { this.task = task; }
@Override
public void run() {
try { task.run(); }
finally {
// 清理所有已知 ThreadLocal (需維護註冊表) 或
// 依賴 ThreadPoolExecutor 的 ThreadLocal 清理機制 (JDK 8+ 有限支援)
}
}
}
remove()。WeakReference 包裝值 (但會增加 GC 壓力)。| 方案 | 適用場景 | 優/缺點 |
|---|---|---|
| ThreadLocal | 請求鏈路追蹤、DB 連接持有、事務上下文 | 簡單高效,需嚴格 remove |
| 方法參數傳遞 | 調用鏈淺、參數少 | 零副作用,但參數爆炸 |
| Context/TransmittableThreadLocal (Alibaba TTL) | 線程池跨任務傳遞上下文 | 解決繼承性問題,引入依賴 |
虛擬執行緒
Copyright © 程式碼之家 All Rights Reserved
|