1. 程式人生 > >ThreadLocal解析:父執行緒的本地變數不能傳遞到子執行緒詳解

ThreadLocal解析:父執行緒的本地變數不能傳遞到子執行緒詳解

    眾所周知,ThreadLocal類是java提供執行緒本地變數的工具類。但父執行緒的本地變數卻不能被子執行緒使用,程式碼如下:

 1 public static void main(String[] args) {
 2         ThreadLocal<String> threadLocal = new ThreadLocal<>();
 3         threadLocal.set("abc");
 4         System.out.println("父執行緒:"+threadLocal.get());
 5         Thread t1 = new Thread(new Runnable() {
 6             @Override
 7             public void run() {
 8                 System.out.print("子執行緒:"+threadLocal.get());
 9             }
10         });
11         t1.start();
12     }

    執行結果如下:

     至於原因呢,得先了解ThreadLocal儲存的變數是怎麼儲存的。首先,讓我們先看看Thread類的原始碼:在thread類中有宣告這麼一個成員變數——threadLocals 

ThreadLocal.ThreadLocalMap threadLocals = null;

     根據定義可以看出,這是ThreadLocal類裡的靜態內部類,它的結構是Map結構,以鍵值對的形式儲存值。key值就是當前ThreadLocal類的例項,value值就是當前執行緒的本地變數。所以執行緒的本地變數是存線上程例項當中的,而不是存在ThreadLocal中。ThreadLocal只是一個工具類,體現在當ThreadLocal例項呼叫set()方法時,會將當前執行緒的threadLocals變數例項化。以下是ThreadLocal類的set()方法原始碼。

public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

    可以看到,先是獲取當前執行緒的例項,再獲取執行緒的成員變數threadLocals,如果threadLocals已經例項化,就直接以當前ThreadLocal類為key,儲存的本地變數為value,存進threadLocals中。所以執行緒本地變數是儲存線上程中的。如果threadLocals未例項化,則呼叫createMap()方法,該方法會呼叫ThreadLocalMap的構造方法,初始化一個以當前ThreadLocal類為key,儲存的本地變數為value的Map。該Map是類似HashMap的,感興趣的話可以繼續往下看原始碼,此處不做擴充套件。

    回到最開始的問題,子執行緒呼叫ThreadLocal類的get方法,原始碼如下:

public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

    此時,會產生兩個分支,一個是通過子執行緒的ThreadLocalMap的getEntry()方法獲取Entry並返回value值,但由於子執行緒的ThreadLocalMap!=父執行緒的ThreadLocalMap,所以獲取不到父執行緒的本地變數。另一個分支是返回setInitialValue()的返回值,setInitialValue()方法原始碼如下:

private T setInitialValue() {
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

    可以看到value會預設為null,如果子執行緒的ThreadLocalMap已經例項化,則直接以ThreadLocal例項為key,null為value存進ThreadLocalMap中,否則就建立一個同上所述的ThreadLocalMap。

    引發出的問題:ThreadLocal類是弱引用,一次GC後會為null,當key為null時,value值卻還存在記憶體中,造成記憶體洩漏。所以ThreadLocal類最後一定要執行remove()方法。在GC之前將記憶體釋放。

    綜上,第一次寫部落格,寫得不好或者有錯誤的地方,請指