1. 程式人生 > >【Java併發學習五】圖解ThreadLocal

【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的值:
image.png

記憶體洩漏

ThreadLocal使用不當是容易發生記憶體洩漏的。原因在於,我們設定完ThreadLocal的值後,該執行緒如果還在執行,ThreadLocalMap中該ThreadLocal所在的Entry不會被回收,一直在記憶體中存在。即存在這種引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry

為了解決這個問題,ThreadLocalMap中的key被設定為了弱引用,即一段時間後沒被使用的話,key值將被GC垃圾回收機制回收。而ThreadLocalget()set()執行時,會檢查ThreaLocalMap中key值為null的Entry,將value去除。

但是這仍然沒有完全解決記憶體洩漏的問題,原因在於,如果該執行緒的再也沒有執行ThreadLocalget()set()方法,則value仍然會一直存在記憶體中,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value

所以,為了避免記憶體洩漏,我們最好在使用完ThreadLocal後,手動remove()掉它