眾所周知,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之前將內存釋放。
綜上,第一次寫博客,寫得不好或者有錯誤的地方,請指正。