簡析ThreadLocal原理及應用
ThreadLocal的源碼加上注釋不超過八百行,源碼結構清晰,代碼也比較簡潔。ThreadLocal可以說是Java中解決多線程數據共享問題方案中的一股清流,該方案為每個線程分配一個獨立的變量副本,各個線程之間的變量互不干擾。下面一起來看看吧:
預計閱讀時間:5分鍾
ThreadLocal的定義與理解
定義和特點:
ThreadLocal顧名思義可以理解為線程本地變量,ThreadLocal將變量的各個副本值保存在各個線程Thread,Thread對象實例采用ThreadLocalMap數據結構來存儲副本值。每個線程往這個ThreadLocal中讀寫是線程隔離,一種將可變數據通過每個線程有自己的獨立副本從而實現線程封閉的機制。
適用場景:
(1) 當很多線程需要多次使用同一個對象,並且需要該對象具有相同初始化值;
(2) 適用於資源共享但不需要維護狀態的情況,也就是一個線程對資源的修改,不影響另一個線程的運行;
(3) 基於ThreadLocal實現線程安全是采用"空間換時間",synchronized順序執行是"時間換取空間"。
ThreadLocal方法及使用示例:
ThreadLocal與其內部類ThreadLocalMap以及線程類Thread聯系緊密,為了分析ThreadLocal類的工作原理,先介紹該類的所有方法(JDK 1.8):
該類的核心方法包括4個:
(1) protected T initialValue()
(2) public T get()
-
先獲取當前線程的thread對象,再獲取thread對象的threadLocalMap對象,然后根據當前的threadLocal對象取得table數組對應下標的Entry對象;
-
如果Thread對象的ThreadLocalMap為空的話,就調用setInitialValue方法,該方法初始化map並且放入null ( initialValue的返回值為null ),可以通過覆蓋該方法修改沒有set時的初始值。
(3) public void set( T value)
-
先調用Thread類的靜態方法獲得當前線程的Thread對象,每個線程對應的Thread對象都有一個ThreadLocalMap對象的引用;
-
獲得當前線程的ThreadLocalMap對象;
-
如果不為空就調用set方法,如果為空就調用createMap方法,傳入參數為ThreadLocalMap為空的Thread對象和T類型的firstValue。
(4) public void remove()
-
先獲取當前線程的Map對象;
-
調用Map的remove方法,刪除entry。
ThreadLocal使用注意事項
1、ThreadLoca對象是一個弱引用
ThreadLocalMap中的節點Entry繼承了WeakReference類,定義了一個類型為Object的value,用於存放塞到ThreadLocal里的值。如果這里使用普通的key-value形式來定義存儲結構,實質上就會造成節點的生命周期與線程強綁定,只要線程沒有銷毀,那么節點在GC分析中一直處於可達狀態,沒辦法被回收,而程序本身也無法判斷是否可以清理節點。ThreadLocal對象是一個繼承自WeakReference的弱引用,當把ThreadLocal的實例置為空以后,沒有任何強引用指向ThreadLocal的實例,所以ThreadLocal的將會被GC回收。生命周期只存活到下次GC前,可降低內存泄漏的風險。
2、ThreadLocal與內存泄漏
ThreadLocal對象是具有弱引用特點,雖然在一定程度上降低了內存泄漏的風險,但是在有線程復用如線程池的場景中,一個線程的壽命很長,大對象長期不被回收影響系統運行效率與安全,那么就存在一條強引用鏈的關系一直存在:Thread --> ThreadLocalMap-->Entry-->Value,最終造成內存泄漏。
如何避免內存泄漏:
調用ThreadLocal的get()、set()方法時完成后再調用remove方法,將Entry節點和Map的引用關系移除,這樣整個Entry對象在GC Roots分析后就變成不可達了,下次GC的時候就可以被回收。
3、哈希沖突怎么解決
ThreadLocalMap中解決哈希沖突的方式並非鏈表的方式,而是采用線性探測的方式,具體來說,就是簡單的步長加1或減1,尋找下一個相鄰的位置。