get()方法
步驟:
1.獲取當前線程的ThreadLocalMap對象threadLocals
2.從map中獲取線程存儲的K-V Entry節點。
3.從Entry節點獲取存儲的Value副本值返回。
4.map為空的話返回初始值null,即線程變量副本為null,在使用時需要注意判斷NullPointerException。
set()方法
步驟:
1.獲取當前線程的成員變量map
2.map非空,則重新將ThreadLocal和新的value副本放入到map中。
3.map空,則對線程的成員變量ThreadLocalMap進行初始化創建,并將ThreadLocal和value副本放入map中。
remove()方法
ThreadLocalMap
ThreadLocalMap是ThreadLocal的內部類,沒有實現Map接口,用獨立的方式實現了Map的功能,其內部的Entry也獨立實現。
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方法。