面試 ThreadLocal,被問懵了?看完這篇文章你就穩了!
引言
小米最近在做社招面試,遇到了一位候選人,面試到 Java 并發時,聊到了 ThreadLocal,候選人一開始信心滿滿:“這個我會!” 結果,深挖幾輪之后,發現這位候選人只會皮毛,甚至踩了不少坑……
于是,今天就和大家聊聊 ThreadLocal 這個面試高頻考點,不僅讓你能答出基礎概念,還能講出實際使用場景,讓面試官對你刮目相看!
什么是 ThreadLocal?
想象一下,你去健身房辦了一張私教卡,每次去健身房,教練都會給你專屬定制的訓練計劃,而不會讓你去練別人的計劃。
ThreadLocal 就像這張 私教卡,它的核心作用就是:讓每個線程都能擁有自己的專屬變量,而不會影響到其他線程。
在 Java 代碼中,我們通常用 ThreadLocal 來 存儲每個線程獨有的數據,避免線程之間的數據污染。來看一個最簡單的例子:
圖片
運行結果:
圖片
每個線程都有自己獨立的 ThreadLocal 變量,即使修改了變量的值,也不會影響其他線程!
ThreadLocal 的工作原理
1. 底層數據結構
ThreadLocal 的底層實現其實是 每個線程內部維護一個 ThreadLocalMap,這個 Map 以 ThreadLocal 變量為 key,具體的值為 value。
簡單來說,每個線程內部都有一個類似這樣的數據結構:
圖片
當我們調用 threadLocal.set(value) 時,數據并不會存儲到 ThreadLocal 對象本身,而是存放在 當前線程的 ThreadLocalMap 里。
2. 內存泄漏問題
ThreadLocal 設計上是弱引用,但 ThreadLocalMap 里的 value 是強引用,如果不手動清理 ThreadLocal.remove(),可能會導致內存泄漏。來看一個坑:
圖片
如果線程池中線程復用,ThreadLocal 沒有 remove(),線程的 ThreadLocalMap 可能無法被回收,從而導致內存泄漏。
最佳實踐: 每次使用完 ThreadLocal,記得調用 remove(),防止內存泄漏!
ThreadLocal 典型使用場景
1. 用戶身份信息存儲(常見)
在 Web 應用中,每個請求通常都有自己的 用戶身份信息,比如 登錄用戶 ID。我們可以用 ThreadLocal 來存儲用戶信息,保證在同一個線程的多個方法調用中,都能訪問到當前用戶的信息。
圖片
使用方式:
圖片
因為 HTTP 請求是多線程并發的,如果使用全局變量存儲 userId,會導致數據污染!但用 ThreadLocal,每個請求的 userId 只存儲在自己的線程中,互不影響。
2. 事務管理(數據庫連接)
在 Spring 的事務管理中,ThreadLocal 被用來存儲 數據庫連接,保證同一個事務中使用同一個數據庫連接。
3. 日志跟蹤(Tracing)
在分布式系統中,我們經常需要給每個請求分配一個唯一的追蹤 ID(Trace ID),用來跟蹤整個請求的執行流程。ThreadLocal 也是一個很好的選擇:
圖片
然后在日志中加上 Trace ID:
圖片
這樣,整個請求在不同的日志中都有相同的 Trace ID,方便排查問題!
ThreadLocal 的優缺點
總結
ThreadLocal 是 Java 并發中的 “線程局部變量”,常用于存儲線程獨有的數據,避免線程間的數據污染。它的常見使用場景包括:
- 用戶身份信息存儲
- 事務管理(數據庫連接)
- 日志追蹤(Tracing)
但使用時一定要注意內存泄漏問題,記得在適當的時機調用 remove() 方法。