ThreadLocal 提供線程局部變量:每個線程擁有獨立的變量副本,互不干擾。本質是「以空間換時間」,用 ThreadLocalMap 替代同步鎖,實現無鎖線程隔離。
// 關鍵方法
void set(T value); // 當前線程設置值
T get(); // 當前線程獲取值
void remove(); // 移除當前線程的值(防內存洩漏)
static ThreadLocal withInitial(Supplier extends S> supplier); // Java 8 惰性初始化
// 內部結構(簡化)
class Thread {
ThreadLocalMap threadLocals; // 每個線程持有一個 Map
}
class ThreadLocalMap {
Entry[] table; // Entry extends WeakReference>
// key: ThreadLocal 對象(弱引用),value: 實際值
}
| 場景 | 作用 | 示例 |
|---|---|---|
| 數據庫連接/事務上下文 | 線程綁定 Connection,實現聲明式事務 | Spring TransactionSynchronizationManager |
| 鏈路追蹤 TraceId | 全鏈路透傳,無需參數傳遞 | SkyWalking / Zipkin / Logback MDC |
| SimpleDateFormat / 非線程安全對象 | 每線程獨享實例,避免併發亂序 | ThreadLocal.withInitial(SimpleDateFormat::new) |
| 用戶身份/權限上下文 | Controller/Filter 設置,Service 直接獲取 | Spring Security SecurityContextHolder |
| 請求級緩存 | 單次請求內共享數據,請求結束自動清理 | MyBatis 一級緩存、Hibernate Session |
public final class TraceContext {
private static final ThreadLocal TRACE_ID = ThreadLocal.withInitial(() -> UUID.randomUUID().toString().replace("-", ""));
private static final ThreadLocal SPAN_ID = ThreadLocal.withInitial(() -> "0");
public static String getTraceId() { return TRACE_ID.get(); }
public static String getSpanId() { return SPAN_ID.get(); }
public static void setTraceId(String traceId) { TRACE_ID.set(traceId); }
public static void setSpanId(String spanId) { SPAN_ID.set(spanId); }
public static void clear() { TRACE_ID.remove(); SPAN_ID.remove(); }
/** 子 Span 生成 */
public static String nextSpanId() {
String parent = SPAN_ID.get();
String child = parent + "." + (parent.split("\\.").length + 1);
SPAN_ID.set(child);
return child;
}
}
// Filter 中調用
@Component @Order(1)
public class TraceFilter implements Filter {
@Override public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
String traceId = ((HttpServletRequest)req).getHeader("X-Trace-Id");
if (traceId != null) TraceContext.setTraceId(traceId);
try { chain.doFilter(req, res); }
finally { TraceContext.clear(); } // 必須清理,防內存洩漏
}
}
// Logback Pattern 引用
%d{yyyy-MM-dd HH:mm:ss} [%X{traceId}] %msg%n
⚠️ 核心原因:
ThreadLocalMap的 Key 是WeakReference。當ThreadLocal對象被 GC 回收(如動態創建的匿名類),Key 變 null,但 Value 強引用仍存活,導致 Entry 永駐 Map,隨線程池線程長期存活 → OOM。
private static final ThreadLocal TL = ThreadLocal.withInitial(...) ,確保 Key 永不被 GC;try { ... } finally { TL.remove(); },尤其在線程池場景;
// 父線程設置
InheritableThreadLocal ctx = new InheritableThreadLocal<>();
ctx.set("parent-value");
// 子線程自動繼承副本
new Thread(() -> System.out.println(ctx.get())).start(); // 輸出 parent-value
注意:線程池復用線程時,不會自動繼承(因非新建線程)。需配合 ThreadPoolTaskDecorator (Spring) 或手動傳遞。
com.alibaba transmittable-thread-local 2.14.1
// TTL 自動傳遞給線程池子任務
TransmittableThreadLocal ttl = new TransmittableThreadLocal<>();
ttl.set("trace-123");
executor.submit(() -> System.out.println(ttl.get())); // 輸出 trace-123
阿里開源 TTL 解決了線程池場景下的上下文傳遞難題,廣泛用於鏈路追蹤、租戶隔離等領域。
ThreadLocal 實現線程隔離,核心場景:上下文傳遞、非線程安全對象複用、請求級緩存;