【Java併發學習五】圖解ThreadLocal
簡單整理下ThreadLocal的原理,以及它需要注意的記憶體洩漏。
ThreadLocal原理
ThreadLocal不多介紹,可看作執行緒內的區域性變數(這個比喻很貼切)。我們平時宣告的區域性變數的範圍一般是方法內的,而ThreadLocal變數的範圍是整個執行緒。
我們先來看一段程式碼demo:
public class Test {
//可看作執行緒內宣告的區域性變數
static ThreadLocal<String> threadLocal = new ThreadLocal<>();
public void a(){
//設定執行緒內區域性變數值為1
threadLocal.set("1");
}
public void b(){
//獲取執行緒內區域性變數值
System.out.println(threadLocal.get()); //輸出:1
//設定執行緒內區域性變數值為2
threadLocal.set("2");
//獲取執行緒內區域性變數值
System.out.println(threadLocal.get()); ////輸出:2
}
public static void main(String[] args) {
Test test = new Test();
test.a();
test.b();
}
}
通過方法a()
設定了執行緒區域性變數ThreadLocal
的值,然後再另一個方法b()
中獲取並修改了它。由於呼叫時方法a()
和b()
都在同一執行緒中,所以可以成功獲取和修改threadLocal
問題:那ThreadLocal是如何做到,使變數的值的使用範圍是整個執行緒的呢?
這主要得益於執行緒Thread
類中的一個成員變數:ThreadLocalMap
。這個Map
的鍵值對是<ThreadLocal,Object>
,key
是ThreadLocal物件,value
是該ThreadLocal物件設定的值。
public class Thread implements Runnable {
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
}
ThreadLocalMap
可看作儲存著該執行緒內所有的 “執行緒區域性變數”的集合。每個執行緒都維護著自己的執行緒區域性變數集合:ThreadLocalMap
:
當我們設定一個ThreadLocal的值myThreadLocal.set("1")
時,其實就是在往該執行緒的成員變數ThreadLocalMap
中新增myThreadLocal-1
的一個元素:
當我們獲取一個ThreadLocal
的值myThreadLocal.get()
時,其實就是從ThreadLocalMap
中獲取key為myThreadLocal
的entry的值:
記憶體洩漏
ThreadLocal
使用不當是容易發生記憶體洩漏的。原因在於,我們設定完ThreadLocal
的值後,該執行緒如果還在執行,ThreadLocalMap
中該ThreadLocal
所在的Entry不會被回收,一直在記憶體中存在。即存在這種引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry
為了解決這個問題,ThreadLocalMap
中的key被設定為了弱引用,即一段時間後沒被使用的話,key值將被GC垃圾回收機制回收。而ThreadLocal
的get()
、set()
執行時,會檢查ThreaLocalMap
中key值為null的Entry,將value去除。
但是這仍然沒有完全解決記憶體洩漏的問題,原因在於,如果該執行緒的再也沒有執行ThreadLocal
的get()
、set()
方法,則value仍然會一直存在記憶體中,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value
所以,為了避免記憶體洩漏,我們最好在使用完ThreadLocal
後,手動remove()
掉它