ThreadLocal實(shí)現(xiàn)原理和應(yīng)用實(shí)例

java多線程編程中,線程安全是個(gè)很重要的概念。在保證線程安全的手段中,除了同步、鎖、不變對(duì)象外,還有一種很重要的方法便是線程封閉技術(shù),就是將可變對(duì)象封閉在一個(gè)線程中,同時(shí)只能由一個(gè)線程訪問(wèn)該對(duì)象。實(shí)現(xiàn)線程封閉的一種常用方法便是使用ThreadLocal類,這個(gè)類能使線程中的某個(gè)值與保存的對(duì)象關(guān)聯(lián)起來(lái)。ThreadLocal為每個(gè)使用變量的線程都存有一份副本,因此get總是返回由當(dāng)前線程set的最新值。那么ThreadLocal如何實(shí)現(xiàn)變量的線程封閉的呢?下面我們通過(guò)對(duì)ThreadLocal的源碼分析來(lái)解讀其實(shí)現(xiàn)原理。

實(shí)現(xiàn)原理

類的關(guān)系和職責(zé)

ThreadLocal中定義了一個(gè)內(nèi)部靜態(tài)類ThreadLocalMap,該類是線程本地變量的容器——一個(gè)類似HashMap的容器。這兩個(gè)類再加上Thread類共同配合實(shí)現(xiàn)了線程封閉技術(shù)。首先,我們來(lái)看一下這幾類的關(guān)系圖

ThreadLocal類關(guān)系圖

從圖中我們可看出這幾個(gè)類有如下重要關(guān)系:

  1. Thread持有一個(gè)包級(jí)別的ThreadLocalMap成員變量threadLocals,兩者屬于聚合關(guān)系

  2. ThreadLocal通過(guò)Thread.currentThread()靜態(tài)方法獲得對(duì)當(dāng)前線程的引用依賴

  3. 由于ThreadLocal類與Thread類在同一個(gè)包下,因此ThreadLocal可以自由訪問(wèn)Thread.threadLocals變量,因此ThreadLocal類則負(fù)責(zé)創(chuàng)建和初始化threadLocals變量

  4. 我們自定義的應(yīng)用類ApplicationClass類只依賴ThreadLocal類,并訪問(wèn)該類的公有方法可以寫入和讀取線程的本地變量

  5. ThreadLocalMap的key是ThreadLocal對(duì)象,value是ThreadLocal中存放的變量

圖中類之間的關(guān)系還是比較繞的,但對(duì)于使用者來(lái)說(shuō)相對(duì)簡(jiǎn)單,我們只關(guān)心ThreadLocal類便可以了。最常用的ThreadLocal類方法public T get()public void set(T value),還有一個(gè)protected作用域的方法protected T initialValue()用于初始化變量值。這里我們會(huì)發(fā)現(xiàn),ThreadLocal類似于Facade設(shè)計(jì)模式的門面類,對(duì)外提供簡(jiǎn)單的接口,對(duì)內(nèi)協(xié)調(diào)Thread和ThreadLocalMap之間的關(guān)系。而真正實(shí)現(xiàn)需求核心邏輯的是類Thread和ThreadLocalMap。

那么,我們可以定義這么一個(gè)需求:我們需要將一個(gè)或多個(gè)可變對(duì)象的封閉在一個(gè)線程中,使得一個(gè)可變對(duì)象在不進(jìn)行同步的情況下,同時(shí)只能有一個(gè)線程可以訪問(wèn)該變量,從而實(shí)現(xiàn)線程安全。對(duì)于這個(gè)需求,我們明確一下三個(gè)類的職責(zé)關(guān)系,可能更容易理解:

  1. Thread類是使線程封閉的主體,對(duì)象的訪問(wèn)權(quán)限就是封閉在當(dāng)前運(yùn)行的線程中,只能由當(dāng)前線程訪問(wèn)(這也是為什么會(huì)由Thread類以聚合的關(guān)系持有ThreadLocalMap對(duì)象)

  2. 我們的需求是可以實(shí)現(xiàn)多個(gè)可變對(duì)象的線程封閉,因此,我們定義了一個(gè)map容器用于存儲(chǔ)多個(gè)可變對(duì)象,這個(gè)類便是ThreadLocalMap類,這個(gè)map的key是ThreadLocal對(duì)象,value是可變對(duì)象(也就是那個(gè)被封閉起來(lái)的寶寶)。

  3. ThreadLocal對(duì)外提供簡(jiǎn)單的方法,同時(shí)由于ThreadLocal和可變對(duì)象是一對(duì)一的關(guān)系,正好可以作為ThreadLocalMap的key與可變對(duì)象做關(guān)聯(lián)。

經(jīng)過(guò)以上的分析,我們會(huì)發(fā)現(xiàn),這個(gè)設(shè)計(jì)非常巧妙,既遵循了類職責(zé)單一的面向?qū)ο笤瓌t,又實(shí)現(xiàn)了我們的需求,同時(shí)對(duì)使用者非常友好。

ThreadLocalMap實(shí)現(xiàn)關(guān)鍵點(diǎn)

通過(guò)以上類關(guān)系和職責(zé)的分析,我們知道ThreadLocal的核心業(yè)務(wù)邏輯其實(shí)是在ThreadLocalMap中的,那么,接下來(lái)我們就重點(diǎn)關(guān)注一下ThreadLocalMap實(shí)現(xiàn)中的幾個(gè)關(guān)鍵點(diǎn)。

從代碼中我們發(fā)現(xiàn),ThreadLocalMap其實(shí)就是一個(gè)簡(jiǎn)版的HashMap,在這個(gè)特定的map中的存儲(chǔ)的是一個(gè)Entry數(shù)組,而ThreadLocalMap中的Entry實(shí)現(xiàn)非常簡(jiǎn)單卻也很特別:

static class ThreadLocalMap {
    /**
     * The table, resized as necessary.
     * table.length MUST always be a power of two.
    */
    private Entry[] table;

    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {
            super(k);
            value = v;
        }
    }
    
    // ...
}

這個(gè)Entry是一個(gè)弱引用,而ThreadLocalMap中的table數(shù)組變量只是持有ThreadLocal對(duì)象的虛引用。這里為什么要這么設(shè)計(jì)呢?我們不妨回憶一下幾種引用的特性。

  1. 強(qiáng)引用(Strong Reference)就是我們最常見(jiàn)的普通對(duì)象引用,只要還有強(qiáng)引用指向這個(gè)對(duì)象,就表明對(duì)象還“活著”,垃圾收集器不會(huì)碰這種對(duì)象。對(duì)于一個(gè)普通的對(duì)象,如果沒(méi)有其他的引用關(guān)系,只要超過(guò)了引用的作用域或者顯式地將相應(yīng)(強(qiáng))引用賦值為 null,就是可以被垃圾收集的了,當(dāng)然具體回收時(shí)機(jī)還是要看垃圾收集策略。
  2. 軟引用(SoftReference)是一種相對(duì)強(qiáng)引用弱化一些的引用,可以讓對(duì)象豁免一些垃圾收集,只有當(dāng) JVM 認(rèn)為內(nèi)存不足時(shí),才會(huì)去試圖回收軟引用指向的對(duì)象。JVM 會(huì)確保在拋出 OutOfMemoryError 之前,清理軟引用指向的對(duì)象。軟引用通常用來(lái)實(shí)現(xiàn)內(nèi)存敏感的緩存,如果還有空閑內(nèi)存,就可以暫時(shí)保留緩存,當(dāng)內(nèi)存不足時(shí)清理掉,這樣就保證了使用緩存的同時(shí),不會(huì)耗盡內(nèi)存。
  3. 弱引用(WeakReference)并不能使對(duì)象被豁免垃圾回收,僅僅提供一種訪問(wèn)在弱引用狀態(tài)下對(duì)象的途徑。被弱引用關(guān)聯(lián)的對(duì)象,在垃圾回收時(shí),如果這個(gè)對(duì)象只被弱引用關(guān)聯(lián)(沒(méi)有任何強(qiáng)引用關(guān)聯(lián)他),那么這個(gè)對(duì)象就會(huì)被回收。這就可以用來(lái)構(gòu)建一種沒(méi)有特定約束的關(guān)系,比如,維護(hù)一種非強(qiáng)制性的映射關(guān)系,如果試圖獲取時(shí)對(duì)象還在,就使用它,否則重新實(shí)例化。它同樣是很多緩存實(shí)現(xiàn)的選擇。
  4. 虛引用(PhantomReference)不能通過(guò)它訪問(wèn)對(duì)象。幻象引用僅僅是提供了一種確保對(duì)象被 finalize 以后,做某些事情的機(jī)制,比如,通常用來(lái)做所謂的 Post-Mortem 清理機(jī)制,也有人利用幻象引用監(jiān)控對(duì)象的創(chuàng)建和銷毀。

這里我們重點(diǎn)關(guān)注一下弱引用的特性:只有弱引用指向的對(duì)象是不影響垃圾收集器對(duì)其回收的。那么,ThreadLocalMap中的Entry為什么定義為ThreadLocal的弱引用類呢?這里我們不妨反向去思考,假設(shè)這里不用虛引用的實(shí)現(xiàn),而是類似HashMap中的強(qiáng)引用,會(huì)有什么問(wèn)題嗎?我們知道除了我們自定義的應(yīng)用類持有ThreadLocal的強(qiáng)引用外,還存在一條線程到ThreadLocal的引用鏈條:Thread.threadLocals --> ThreadLocalMap.table --> ThreadLocal對(duì)象。在很多應(yīng)用尤其是后端服務(wù)器應(yīng)用中,會(huì)創(chuàng)建很多線程,并且線程的生命的周期往往比很多對(duì)象生命周期要長(zhǎng)很多。在使用ThreadLocal的過(guò)程中,不在引用作用域范圍內(nèi)的ThreadLocal對(duì)象或手動(dòng)置空(threadLocal=null)的ThreadLocal對(duì)象,是可以被垃圾回收掉的。而如果在線程到ThreadLocal的引用鏈中是強(qiáng)引用,那么這個(gè)ThreadLocal對(duì)象的生命周期將伴隨著Thread對(duì)象的存活而一直存在。這其實(shí)是不利于垃圾回收和內(nèi)存利用的,相當(dāng)于白白浪費(fèi)了ThreadLocal對(duì)象占用的內(nèi)存空間。因此,為了不影響垃圾收集器對(duì)ThreadLocal變量的回收,這里Entry的實(shí)現(xiàn)用了WeakReference。

從上面代碼分析中,我們也會(huì)了解到為了盡快實(shí)現(xiàn)垃圾收集器對(duì)ThreadLocal以及相關(guān)聯(lián)的對(duì)象的回收,對(duì)于我們不再使用的ThreadLocal變量,最好將相關(guān)的強(qiáng)引用置空,以避免內(nèi)存溢出。代碼如下示例所示:

// threadLocal是事先定義好的ThreadLocal變量
threadLocal.remove();
threadLocal = null;

應(yīng)用實(shí)例

在一些框架或基礎(chǔ)類庫(kù)中,ThreadLocal的使用還是比較廣泛的。下面就舉幾個(gè)常見(jiàn)的例子:

concurent包中的應(yīng)用

在讀寫鎖java.util.concurrent.locks.ReentrantReadWriteLock實(shí)現(xiàn)中,ThreadLocal用于記錄當(dāng)前線程持有讀鎖的次數(shù):

/**
 * ThreadLocal subclass. Easiest to explicitly define for sake
 * of deserialization mechanics.
*/
static final class ThreadLocalHoldCounter
    extends ThreadLocal<HoldCounter> {
    public HoldCounter initialValue() {
        return new HoldCounter();
    }
}
/**
 * The number of reentrant read locks held by current thread.
 * Initialized only in constructor and readObject.
 * Removed whenever a thread's read hold count drops to 0.
*/
private transient ThreadLocalHoldCounter readHolds;

spring中的事務(wù)管理

spring中的事務(wù)管理,也大量使用了ThreadLocal,代碼如下:

// spring-core中定義的NamedThreadLocal
public class NamedThreadLocal<T> extends ThreadLocal<T> {
    private final String name;

    public NamedThreadLocal(String name) {
        Assert.hasText(name, "Name must not be empty");
        this.name = name;
    }

    public String toString() {
        return this.name;
    }
}
// spring-tx包中定義的事務(wù)管理器
public abstract class TransactionSynchronizationManager {

    private static final Log logger = LogFactory.getLog(TransactionSynchronizationManager.class);

    private static final ThreadLocal<Map<Object, Object>> resources =
            new NamedThreadLocal<>("Transactional resources");

    private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
            new NamedThreadLocal<>("Transaction synchronizations");

    private static final ThreadLocal<String> currentTransactionName =
            new NamedThreadLocal<>("Current transaction name");

    private static final ThreadLocal<Boolean> currentTransactionReadOnly =
            new NamedThreadLocal<>("Current transaction read-only status");

    private static final ThreadLocal<Integer> currentTransactionIsolationLevel =
            new NamedThreadLocal<>("Current transaction isolation level");

    private static final ThreadLocal<Boolean> actualTransactionActive =
            new NamedThreadLocal<>("Actual transaction active");
    // ...
}

在spring的事務(wù)管理框架通過(guò)將事務(wù)上下文信息保存在ThreadLocal對(duì)象中,實(shí)現(xiàn)了事務(wù)上線文與執(zhí)行線程的關(guān)聯(lián)關(guān)系,從而使事務(wù)管理與數(shù)據(jù)訪問(wèn)服務(wù)解耦,同時(shí)也保證了多線程環(huán)境下connection的線程安全問(wèn)題。

避免濫用

不過(guò),開(kāi)發(fā)人員也經(jīng)常會(huì)濫用ThreadLocal,例如將所有全局變量都作為ThreadLocal對(duì)象,或者作為一種“隱藏”的傳參手段。但是ThreadLocal也有全局變量的缺點(diǎn),它降低了代碼的可重用性,并在類之間引入隱含的耦合性,因此,在使用時(shí)需要謹(jǐn)慎,必要的情況下再使用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,797評(píng)論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,179評(píng)論 3 414
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 175,628評(píng)論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 62,642評(píng)論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,444評(píng)論 6 405
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 54,948評(píng)論 1 321
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,040評(píng)論 3 440
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,185評(píng)論 0 287
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,717評(píng)論 1 333
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,602評(píng)論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,794評(píng)論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,316評(píng)論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,045評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,418評(píng)論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 35,671評(píng)論 1 281
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,414評(píng)論 3 390
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,750評(píng)論 2 370

推薦閱讀更多精彩內(nèi)容