🧠 一、什么是
ThreadLocal
?
ThreadLocal
是 Java 提供的一種 線程本地變量機制;每個線程都維護一份自己的副本;
它不用于多個線程共享變量,而是用于每個線程獨立維護自己的變量副本;
常用于:用戶上下文、數據庫連接、格式化對象(如
SimpleDateFormat
)、日志跟蹤等場景。
🚀 二、
ThreadLocal
的基本用法ThreadLocal<String> local = new ThreadLocal<>(); local.set("hello"); // 設置當前線程的副本 String val = local.get(); // 獲取當前線程的副本 local.remove(); // 手動刪除,防止內存泄漏
每個線程訪問的是 自己的變量副本,彼此隔離。
🧱 三、
Thread
、ThreadLocal
、ThreadLocalMap
三者關系圖Thread (線程對象)└── ThreadLocalMap (每個線程獨有的 map)└── Entry[] 數組├── key:ThreadLocal 對象(弱引用)└── value:真正的變量值
? 總結對應關系:
角色 說明 Thread
每個線程都有一個 ThreadLocalMap
ThreadLocal
作為 key 存在于 ThreadLocalMap
中,指向當前線程的副本ThreadLocalMap
是 Thread
內部的屬性,負責存儲每個ThreadLocal
對應的數據
🎯 四、為什么
ThreadLocalMap
的 key 是 弱引用?這個想要了解更詳細可以看博主的另一篇博客:ThreadLocal--ThreadLocal 竟可能導致內存泄漏?看看 ThreadLocalMap 的弱引用機制-CSDN博客
? Java 源碼:
static class Entry extends WeakReference<ThreadLocal<?>> {Object value; }
? 原因:防止內存泄漏(重點)
如果
ThreadLocal
是 強引用:
即使我們不再使用
ThreadLocal
,它依然會作為 key 強引用存在,永遠不會被 GC;而且
ThreadLocalMap
屬于Thread
,Thread
不結束就不會釋放內存;久而久之,value 也無法回收,造成 內存泄漏。
? 如果是 弱引用:
當開發者不再持有
ThreadLocal
引用時,它會被 GC 回收;GC 后
ThreadLocalMap
中 key 為 null;如果調用
ThreadLocal.get()
/set()
,會清除掉這些 stale entry(陳舊數據);? 避免內存泄漏。
💣 五、ThreadLocal 內存泄漏陷阱
問題場景:
線程池中線程長時間不銷毀;
ThreadLocal 被 GC 回收,但
ThreadLocalMap
的 value 還存在;如果不調用
.remove()
,value 永遠不會清理;解決方式:
? 使用完后調用
ThreadLocal.remove()
清理;? 或者用
try-finally
包裝使用邏輯:private static final ThreadLocal<SimpleDateFormat> formatter = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));public void parseDate(String dateStr) {try {formatter.get().parse(dateStr);} finally {formatter.remove(); // 手動清除,避免內存泄漏} }
🧰 六、ThreadLocalMap 的實現細節
本質上是一個自定義的哈希表:
數組結構 + 開放尋址法(沖突后線性探測)
不是
HashMap
,也不是ConcurrentHashMap
數組大小默認
16
,按需擴容(最多 2^30)
🛠 七、常用方法詳解
方法 說明 set(T value)
設置當前線程副本中的值 get()
獲取當前線程副本中的值 remove()
刪除當前線程副本中的值 withInitial(Supplier)
構造帶默認初始值的 ThreadLocal
? 示例:使用默認初始值的
ThreadLocal
ThreadLocal<Integer> counter = ThreadLocal.withInitial(() -> 0);public void increment() {counter.set(counter.get() + 1); }
🌟 八、
InheritableThreadLocal
:子線程繼承父線程值InheritableThreadLocal<String> local = new InheritableThreadLocal<>(); local.set("父線程值");new Thread(() -> {System.out.println(local.get()); // 子線程能讀取父線程設置的值 }).start();
適合:父線程傳遞上下文,如用戶ID、請求ID 等。
🧭 九、實際應用場景
場景 示例 ? 用戶上下文 登錄后存放用戶信息: ThreadLocal<User>
? DateFormat SimpleDateFormat
非線程安全,放入ThreadLocal
? 數據源切換 動態數據源管理,存放在 ThreadLocal
中? Trace ID 日志鏈路追蹤,全鏈路唯一 ID 存 ThreadLocal ? Spring事務/安全 Spring 的 TransactionSynchronizationManager
、SecurityContextHolder
都用到了ThreadLocal
📌 十、總結
項目 內容 本質 每個線程一個變量副本 原理 每個線程有一個 ThreadLocalMap 結構 key 為弱引用的 ThreadLocal,value 為副本值 弱引用原因 防止內存泄漏,GC 后 key=null 自動清理 使用建議 用完及時調用 remove()
延伸功能 InheritableThreadLocal
實現值傳遞應用場景 用戶信息、日期格式化、日志追蹤、數據庫連接等