ThreadLocal詳解
概述
做Java開發的對ThreadLocal的肯定不會陌生, 它的作用是提供執行緒內的區域性變數,這種變數在多執行緒環境下訪問時能夠保證各個執行緒裡變數的獨立性。也是Java面試的必備考點。
TheadLocal典型用法
我們知道SimpleDateFormat是非執行緒安全,在多執行緒使用下可能會有問題。
針對這個問題,可以有幾種解決方案:
-
每次要使用 SimpleDateFormat 時都建立一個區域性的 SimpleDateFormat物件。區域性變數,自然就不存線上程安全的問題了。但如果需要頻繁進行呼叫的話,每次都要建立新的物件,開銷太大。
-
就是對 SimpleDateFormat 進行加鎖,這樣可以確保同一時間只有一個執行緒可以持有鎖,進而解決執行緒安全的問題。但是這種方法在多執行緒競爭激烈的時候會帶來效率問題
-
就是使用 ThreadLocal。 ThreadLocal 可以確保每個執行緒都可以得到單獨的一個 SimpleDateFormat 的物件,那麼自然也就不存在競爭問題了。
TheadLocal原理分析
TheadLocal的內部結構示意圖如下:
從結構示意圖真正儲存值得地方是放在了執行緒物件中,Thread物件中有個ThreadLocalMap物件,該物件中的key是ThreadLocal物件,value是使用者真正儲存的值。
以ThreadLocal 中的set為例:
在ThreadLocal的 set方法中,首先獲取了當前執行緒,然後獲取執行緒中的ThreadLocalMap,如果map不會null,則直接put到該map中,其中key就是threadLocal物件,value即使用者set的值。
ThreadLocal 如何避免記憶體洩漏的呢
如果ThreadLocalMap中的key和value都是普通物件,可能會存在什麼問題呢?
當用戶如果忘記remove物件時並且該執行緒一直存在時,就會就會造成執行緒對key、value的引用一直存在造成記憶體洩漏。
為了避免記憶體洩漏,ThreadLocalMap中的key採用了WeakReference。
這樣保證了即使使用者忘記呼叫remove,也能保證key被回收調。
那麼value怎麼辦呢,因為value不是 WeakReference,那麼JDK是如何保證value不存在記憶體洩漏呢?
其實在 ThreadLocalMap有個expungeStaleEntry, 這個方法在ThreadLocalMap get、set、remove、rehash等方法都會呼叫到。 看下面標紅的兩處程式碼,第一處是將remove的entry賦空,第二次處是找到已經被GC的ThreadLocal,然後會清理掉table陣列對entry的引用。這樣entry在後續的GC中就會被回收。
ThreadLocal最佳實踐
-
在使用完成之後,要顯示的呼叫remove釋放。
-
官方推薦使用private static修飾。其中private比較容易理解;加上static之後,就不會頻繁的建立弱引用,這樣就能減少弱引用對GC的影響。