天天看點

多線程之ThreadLocal了解、應用及源碼分析

        一、引入:為什麼要使用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()方法上面已經介紹過了,這裡就不再贅述了。