一、引入:為什麼要使用ThreadLocal
之前,在某位大牛的一篇文章中,看到了實作線程安全的幾個層次,分别是:
1、使用無狀态的對象
無狀态對象也就是不變的對象,它是最安全的,是以不需要考慮線程間同步等安全性問題;
2、做到線程封閉
線程封閉就是把對象封裝到單個線程裡,隻有這一個線程才能看到該對象;
3、采用同步技術
比如采用synchronized關鍵字,鎖定某個類、某個方法或者某個代碼塊,或者使用互斥鎖、讀寫鎖等鎖技術,最根本的就是讓某個對象,或者某個方法,或者某個代碼塊,在某一時刻,始終隻能有一個線程操縱。
對于第一個使用無狀态的對象,它的應用場景非常有限,而大型複雜系統,往往需要大量有狀态的對象;而對于第三個采用同步技術,它的實作比較複雜,需要考慮線程間同步問題,稍有不慎,就會寫出錯誤的代碼,帶來毀滅性的打擊,并且,線程間同步技術需要使用鎖等,使得很多線程可能處于等待狀态,會帶來性能方面的消耗。
我們今天單說下線程封閉。何謂線程封閉?線程封閉就是把對象封裝到單個線程裡,隻有這一個線程才能看到該對象。它可以通過以下三種方式實作:
1、ad-hoc線程封閉;
2、棧封閉;
3、使用ThreadLocal。
ad-hoc線程封閉就是完全靠實作者控制的線程封閉,這個我們今天不說。而棧封閉雖然概念顯得高大上,其實就一句話,多使用局部變量。為什麼呢?這就要從JVM的運作時記憶體模型說起,局部變量的引用是保持線上程棧中的,隻對目前線程可見,其他線程不可見,是以說局部變量是線程安全的。而第三種方式--使用ThreadLocal,那麼問題來了,ThreadLocal是什麼,它又是如何實作線程封閉的呢?
二、入題:什麼是ThreadLocal
ThreadLocal有一個名稱,叫做線程本地變量。它為變量在每個線程中都建立了一個副本,那麼每個線程可以通路自己内部的副本變量。由此可知,它采用了一種空間換時間的政策,來保證線程間的安全性。它實際上也是程式控制線程封閉的一種機制,不過這種機制是Java自身提供的罷了。
三、了解:ThreadLocal内部是如何實作的?
既然ThreadLocal為變量在每個線程中都建立了一個副本,那麼我們先來看下它是如何存取的。在此之間,我們先介紹下線程Thread類中一個十分重要的成員變量threadLocals,其定義如下:
實際上,ThreadLocalMap就是一種Map結構的資料類型。它的Key為ThreadLocal執行個體,而value則是任意一個Object。而ThreadLocalMap中存儲key-value的是其内部類Entry,定義如下:
它繼承自WeakReference,是對ThreadLocal執行個體的一種弱引用。而弱引用的作用其實很簡單,當一個對象僅僅被weak reference指向, 而沒有任何其他strong reference指向的時候, 如果GC運作, 那麼這個對象就會被回收。
好了,言歸正傳。我們還是先說下ThreadLocal是如何為變量在每個線程中都建立了一個副本,也就是它的存取方法。
先看存,存是依靠ThreadLocal的set()方法實作的,代碼如下:
邏輯比較簡單,大體流程如下:
1、擷取目前線程t;
2、調用getMap()方法,獲得目前線程t對應的ThreadLocalMap,即map:
ThreadLocalMap中存儲的是ThreadLocal執行個體到value的映射,而value就是我們想線上程中持有的變量副本;
3、判斷map是否存在:
3.1、如果map存在,将目前ThreadLocal執行個體this與傳入的value映射關系放入map;
3.2、如果map不存在,調用createMap()方法,建立目前線程t對應的ThreadLocalMap,并加入value值。
我們先看下getMap()方法,代碼如下:
getMap()方法其實擷取就是上面我們提到過的對應線程t的threadLocals變量。
接下來,我們再看下createMap()方法,代碼如下:
createMap()方法就是構造一個ThreadLocalMap執行個體,指派給線程t的threadLocals變量。而構造ThreadLocalMap執行個體時,傳入的key為目前ThreadLocal執行個體this,value則是我們想線上程中持有的變量副本。
存資料講完了,我們再看下取資料。而取資料則是通過ThreadLocal的get()方法實作的,代碼如下:
取資料的get()方法也比較簡單,大體邏輯如下:
1、獲得目前線程t;
3、判斷map是否為空:
3.1、如果map不為空,利用目前ThreadLocal執行個體this,從map中擷取對應的條目Entry,即e,Entry的key就是ThreadLocal執行個體this,value對應為實際需要的value;如果e不為空,直接傳回e中的value,并轉化為T類型;
3.2、如果map為空,調用setInitialValue()傳回value。
getMap()方法我們在上面已經講解過了,這裡我們隻看下setInitialValue()方法,代碼如下:
setInitialValue()方法,說白了,就是設定初始化的value值。它的處理流程如下:
1、調用initialValue()初始化value,initialValue是一個空方法,傳回的為null,可以根據需要進行重寫;
2、擷取目前線程t;
3、調用getMap()方法,獲得目前線程t對應的ThreadLocalMap,即map:
4、判斷map是否存在:
4.1、如果map存在:将目前ThreadLocal執行個體this與初始化的value放入map;
4.2、如果map不存在:調用createMap()方法,建立目前線程t對應的ThreadLocalMap,并加入value值;
5、傳回value。
其中,createMap()方法上面已經介紹過了,這裡就不再贅述了。