ThreadLocal 是什麼鬼?用法、源碼一鍋端

ThreadLocal 是一個老生常談的問題,在源碼學習以及實際項目研發中,往往都能見到它的蹤影,用途比較廣泛,所以有必要深入一番。

敢問,ThreadLocal 都用到了哪裡?有沒有運用它去解決過業務問題呢?

沒用過、答不上來也沒關係,因為通過今天的分享,能讓你輕鬆 get 如下幾點,收穫滿滿。

  • ThreadLocal 快速入門;
  • ThreadLocal 源碼解讀;
  • ThreadLocal 使用場景;ThreadLocal 阿里規約中的奇技淫巧。

ThreadLocal 快速入門

理論暫且不談,ThreadLocal 到底該怎麼用?don't talk, show me the code!

ThreadLocal 是什麼鬼?用法、源碼一鍋端

上圖是老項目真實在用的一個場景,主要藉助 ThreadLocal 統計請求處理的耗時。仔細去看 ThreadLocal 使用起來其實蠻簡單,接下來通過一段代碼,讓你快速掌握 ThreadLocal 的使用。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

如上面代碼所示,模擬一個業務請求處理耗時的場景,我們跑起來,看一看。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

雖然代碼能跑起來,充其量只是帶你熟練使用了一把 ThreadLocal 的 API,並沒有充分體會到 ThreadLocal 的核心設計理念。

看官別急,容我稍微修飾修飾代碼,請看仔細。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

代碼調整很簡單,就是把 main 方法中的代碼,挪到線程體內去執行,然後看看獲取請求開始時設置的時間值,是否會在多線程情況下而發生錯亂?代碼不會騙人的,跑起來看一看。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

依據程序結果,就可以簡單對 ThreadLocal 做個小結。

第一:對於 ThreadLocal 而言,最常用的 API,就是 get、set、remove,其實還有 initialValue(常用來在創建 ThreadLocal 對象時設置初始值);

第二:針對程序輸出的結果而言,站在線程的角度去看,就好像每一個線程都完全擁有 ThreadLocal 的變量,感覺就是為每一個使用該變量的線程都提供一個變量值的副本,使每一個線程都可以獨立的改變自己的副本,而不會和其它線程的副本發生衝突。

第三:坊間說 ThreadLocal 是 Thread Local Variable(線程局部變量)的意思,或許將它命名為 ThreadLocalVar 會更加合適。

總結起來就一句「通過 ThreadLocal 能達到線程隔離的機制」,這句話真的對嗎?其實是要持懷疑態度的。

don’t talk, show me the code!代碼不會騙人的,拿出證據來。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

上面代碼是假想的一個場景,主要看代碼。按照 ThreadLocal 的設計理念,會直接斷言每個線程的序列號獨立維護,互不影響。

可是結果卻差點意思,居然沒有達到線程隔離的效果,程序真實輸出如下。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

現象:當 ThreadLocal 設置的 value 都指向同一個對象(示例中的 FlowNo 對象),這個時候 ThreadLocal 就失靈啦(其實是有點難理解,沒關係,後面有圖解釋)。

煙未滅,酒過半,是時候走進 JDK 源碼看一看。

ThreadLocal 源碼解讀

首先從常用的 set 方法作為切入點,若搞懂這個方法,把 ThreadLocal 差不多就看穿啦。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

如紅色圈住部分代碼,簡單釋義。

a)首先獲取當前線程的對象 t;

b)然後獲取 t 對應的成員變量 ThreadLocalMap;

c)接著判斷 ThreadLocalMap 是否為空,不為空則將 ThreadLocal 和新的 value 放入到 ThreadLocalMap 中;

d)如果 ThreadLocalMap 為空,則對線程的成員變量 ThreadLocalMap 進行初始化操作,並將 ThreadLocal 和 value 放入 ThreadLocalMap 中。

哎呦,我去!ThreadLocal 剛用明白,這 ThreadLocalMap 又是什麼鬼?別急,我們慢慢細看。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

通過上面源碼,可以清楚的知道 ThreadLocalMap 是 ThreadLocal 中的一個靜態內部類,而 ThreadLocalMap 裡面定義了一個靜態的內部類 Entry 來保存數據,在 Entry 內部使用 ThreadLocal 作為 key,而 value 就是要設置的值(WeakReference,稍微留意一下,後面會再次提及)。

說了這麼多,感覺苦澀的文字,不如粗糙的圖一張(想著點開篇的代碼,說不定就醍醐灌頂啦,記住這個圖就行啦)。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

還記得開篇案例最後一個現象嗎?當 ThreadLocal 設置的 value 都指向同一個對象,ThreadLocal 就失靈啦。

依據上圖,如果設置的 value 初始值均都指向同一個對象時(指的是Entry的value),多線程情況下,不發生影響才怪。

另外,對照著上面的圖,再去看 get 方法,就相對好理解很多啦,不再貼代碼,直接去看 remove 方法的源碼。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

remove 方法很簡單,主要把 ThreadLocal 對象做為 key 從 ThreadLocalMap 清除對應的 Entry。

remove 方法的用途在哪裡?結合下面下面這個繼承關係圖去說說。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

依據上圖所示,很明顯 Entry 的 Key 是一個 WeakReference 弱引用(ThreadLocal 使用到了弱引用),極端情況下可能會發生內存洩露,所以代碼上最終建議調用 remove 方法釋放內存,避免發生內存洩露。

本次源碼剖析就到這裡,接下來我們看看 ThreadLocal 的主要使用場景。

ThreadLocal 使用場景

ThreadLocal 使用場景其實非常多,下面簡單列舉幾個。

a) Java 日誌門面 org.sl4j.MDC 底層使用 ThreadLocal 來保證線程之間的數據隔離及數據傳遞;

b) Hiberante 的Session工具類 HibernateUtil,借用 ThreadLocal 用於 session 管理(老項目還在用);

c)分佈式鏈路跟蹤;

d)類似項目研發中統計方法耗時,記錄登錄 Session 信息,用戶 ID 等等;

e) JDK 7 之後提供的隨機數生成器 ThreadLocalRandom,底層也借用 ThreadLocal 來實現。

ThreadLocal 阿里規約中的奇技淫巧

ThreadLocal 是什麼鬼?用法、源碼一鍋端

【強制】必須回收自定義的 ThreadLocal 變量,尤其在線程池場景下,線程經常會被複用, 如果不清理自定義的 ThreadLocal 變量,可能會影響後續業務邏輯和造成內存洩露等問題。儘量在代理中使用 try-finally 塊進行回收。

<code>正例:
objectThreadLocal.set(userInfo);
try {
// ...
} finally {
objectThreadLocal.remove();
}/<code>

【參考】ThreadLocal 對象使用 static 修飾,ThreadLocal 無法解決共享對象的更新問題。

說明:這個變量是針對一個線程內所有操作共享的,所以設置為靜態變量,所有此類實例共享此靜態變量,也就是說在類第一次被使用時裝載,只分配一塊存儲空間,所有此類的對象(只要是這個線程內定義的)都可以操控這個變量。

ThreadLocal 是什麼鬼?用法、源碼一鍋端

ThreadLocal 是什麼鬼?用法、源碼一鍋端

阿里開發規約對於 ThreadLocal 推薦使用約定,勢必對你會有一定的參考價值。

寫在最後

行文至此,接近尾聲,本次主要帶你對 ThreadLocal 進行快速入門,並通過剖析源碼,帶你知曉 ThreadLocal 背後的東西,最後對阿里開發規約中 ThreadLocal 的使用約定簡單羅列,相信會對你實踐有一定的指導意義。

本次分享就到這裡,希望對你有所幫助吧。

一起聊技術、談業務、噴架構,少走彎路,不踩大坑。歡迎繼續關注「一猿小講」,會持續輸出原創精彩分享,敬請期待!


分享到:


相關文章: