參考網址: https://www.jianshu.com/p/98b68c97df9b (此處連結不知為何,點選後跳轉頁面還是本頁,建議複制打開)
1、ThreadLocal是什麼?
線程本地變量,其為每個使用該變量的線程提供獨立的變量副本,是以每個線程都可以獨立的改變自己的副本,而不影響其它線程維護的副本。
在每個線程内部都有自己的一個Map,該Map是ThreadLocal中的一個内部類ThreadLocalMap。線上程對其進行讀寫時,是從本身擁有的成員變量threadlocals(類型為内部類ThreadLocalMap)中進行讀寫,該變量是用來存儲實際變量副本的。存儲時鍵是目前ThreadLocal變量,值是要儲存的變量複本。
擴充:Thread中包含兩個ThreadLocal中的ThreadLocalMap成員變量,threadLocals、inheritableThreadLocals
2、ThreadLocal是如何為每個線程建立變量的副本的?
在每個線程Thread内部有一個ThreadLocal.ThreadLocalMap類型的成員變量threadLocals,鍵是目前ThreadLocal變量,值是變量的副本;
初始時,在Thread裡面,threadLocals為空,當通過ThreadLocal變量調用get()或set()時,就會對Thread類中的threadLocals進行初始化,并且以目前ThreadLocal變量為鍵,以ThreadLocal要儲存的副本變量為value;
3、ThreadLocalMap的解釋
是ThreadLocal的内部類,類似于HashMap,但并沒有實作Map接口
初始容量是16的Entry<K,V>數組,這裡指定K為ThreadLocal對象,并且注意Entry<K,V>是繼承自WeakReference(弱引用,生命周期隻能存活到下次GC前)
與HashMap最大的不同是,Entry<K,V>沒有next,就是說,解決Hash沖突的方式并不是連結清單的方式,而是采用線性探測法,簡單的步長加一或減一(尋找相鄰的位置)。該線性探測法解決hash沖突的效率低。
線性探測法:根據初始key的hashcode值确定元素的位置,若該位置已存在一個元素,則根據一定的算法機制去下一處位置,直到存放元素成功。
4、ThreadLocal的get()和set()方法解釋
get():
擷取目前線程的ThreadLocalMap對象ThreadLocals;
從map中擷取線程存儲的Entry<K,V>節點;
從Entry<K,V>節點擷取存儲的Value副本值傳回;
map為空的話傳回初始值null,即線程變量副本為null,在使用時需要注意判斷NullPointerException。
set():
擷取目前線程的成員變量map;
map非空,則重新将ThreadLocal和新的value副本放入到map中;
map空,則對線程的成員變量ThreadLocalMap進行初始化建立,并将ThreadLocal和value副本放入map中。
5、ThreadLocal的問題,會造成記憶體洩露
ThreadLocalMap中的鍵是弱引用的,而value是強引用的,這樣會導緻ThreadLocal在沒有外部對象強引用時,發生GC時弱引用key會被回收,而value不會被回收。此時建立ThreadLocal的線程還在運作,時間過長後,加之Entry對象的value一直得不到回收,發生記憶體洩露。
為此,在調用ThreadLocal的get()、set()後,需要調用remove()方法,将Entry節點和Map的引用關系溢出,這樣整個Entry對象在GC Roots分析後就變成不可達了,下次GC時就可以完成正确的回收。
6、ThreadLocal的應用場景
隻适用于獨立副本的情況,例如資料庫連接配接、Session管理等