深入解析ThreadLocal

get()方法


image.png

步驟:
1.獲取當前線程的ThreadLocalMap對象threadLocals
2.從map中獲取線程存儲的K-V Entry節點。
3.從Entry節點獲取存儲的Value副本值返回。
4.map為空的話返回初始值null,即線程變量副本為null,在使用時需要注意判斷NullPointerException。

set()方法

image.png

步驟:
1.獲取當前線程的成員變量map
2.map非空,則重新將ThreadLocal和新的value副本放入到map中。
3.map空,則對線程的成員變量ThreadLocalMap進行初始化創建,并將ThreadLocal和value副本放入map中。

remove()方法


image.png

ThreadLocalMap

ThreadLocalMap是ThreadLocal的內部類,沒有實現Map接口,用獨立的方式實現了Map的功能,其內部的Entry也獨立實現。

image

ThreadLocalMap類圖

在ThreadLocalMap中,也是用Entry來保存K-V結構數據的。但是Entry中key只能是ThreadLocal對象,這點被Entry的構造方法已經限定死了。
Entry繼承自WeakReference(弱引用,生命周期只能存活到下次GC前),但只有Key是弱引用類型的,Value并非弱引用。

ThreadLocalMap的成員變量:

Hash沖突怎么解決

和HashMap的最大的不同在于,ThreadLocalMap結構非常簡單,沒有next引用,也就是說ThreadLocalMap中解決Hash沖突的方式并非鏈表的方式,而是采用線性探測的方式,所謂線性探測,就是根據初始key的hashcode值確定元素在table數組中的位置,如果發現這個位置上已經有其他key值的元素被占用,則利用固定的算法尋找一定步長的下個位置,依次判斷,直至找到能夠存放的位置。

ThreadLocalMap解決Hash沖突的方式就是簡單的步長加1或減1,尋找下一個相鄰的位置。

顯然ThreadLocalMap采用線性探測的方式解決Hash沖突的效率很低,如果有大量不同的ThreadLocal對象放入map中時發送沖突,或者發生二次沖突,則效率很低。

所以這里引出的良好建議是:每個線程只存一個變量,這樣的話所有的線程存放到map中的Key都是相同的ThreadLocal,如果一個線程要保存多個變量,就需要創建多個ThreadLocal,多個ThreadLocal放入Map中時會極大的增加Hash沖突的可能。

ThreadLocalMap的問題

由于ThreadLocalMap的key是弱引用,而Value是強引用。這就導致了一個問題,ThreadLocal在沒有外部對象強引用時,發生GC時弱引用Key會被回收,而Value不會回收,如果創建ThreadLocal的線程一直持續運行,那么這個Entry對象中的value就有可能一直得不到回收,發生內存泄露。

如何避免泄漏
既然Key是弱引用,那么我們要做的事,就是在調用ThreadLocal的get()、set()方法時完成后再調用remove方法,將Entry節點和Map的引用關系移除,這樣整個Entry對象在GC Roots分析后就變成不可達了,下次GC的時候就可以被回收。

如果使用ThreadLocal的set方法之后,沒有顯示的調用remove方法,就有可能發生內存泄露,所以養成良好的編程習慣十分重要,使用完ThreadLocal之后,記得調用remove方法。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容