多線程知識梳理(9) - ThreadLocal

一、基本概念

1.1 ThreadLocal 的用途

首先饵撑,我們來看一下JDK源碼中對于ThreadLocal的解釋:

This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one has its own, independently initialized copy of the variable. ThreadLocal instances are typically privatestatic fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

翻譯過來就是:

ThreadLocal用來提供線程內(nèi)的局部變量开仰。這些變量在多線程環(huán)境下訪問時能夠保證各個線程里的變量相對獨立于其它線程內(nèi)的變量,ThreadLocal實例通常來說都是private static類型的瞧柔。

因此语稠,ThreadLocal適用于滿足下面條件的場景:

  • 每個線程 有且僅有 該對象的一個實例
  • 在該線程的整個生命周期內(nèi) 有多處用到 該實例
  • 存在 多線程訪問 的情況

1.2 ThreadLocal 的使用

ThreadLocalAPI很簡單宋彼,它包含以下四個簽名:

  • get:獲取ThreadLocal中當(dāng)前線程共享變量的值。
  • set:設(shè)置ThreadLocal中當(dāng)前線程共享變量的值仙畦。
  • remove:移除ThreadLocal中當(dāng)前線程共享變量的值宙暇。
  • initialValueThreadLocal沒有被當(dāng)前線程賦值時或當(dāng)前線程剛調(diào)用remove方法后調(diào)用get方法,返回此方法值议泵。

我們用下面的一小段例子,來熟悉一下ThreadLocal的使用桃熄。

class ThreadLocalSamples {

    private static ThreadLocal<Integer> sThreadLocal = new ThreadLocal<Integer>() {

        @Override
        protected Integer initialValue() {
            return 5;
        }

    };

    static void startSample() {
        for (int i = 0; i < 3; i++) {
            new SampleThread("thread_" + i).start();
        }
    }

    private static class SampleThread extends Thread {

        private String mThreadName;

        SampleThread(String threadName) {
            mThreadName = threadName;
        }

        @Override
        public void run() {
            for (int j = 0; j < 5; j++) {
                try {
                    long sleep = (long) (Math.random() * 50);
                    Thread.sleep(sleep);
                    int result = sThreadLocal.get();
                    sThreadLocal.set(++result);
                    Log.d("ThreadLocalSamples", "ThreadName=" + mThreadName + ",result=" + result);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

運行結(jié)果:

運行結(jié)果

從打印的結(jié)果可以看到先口,雖然這3個線程訪問是同一個ThreadLocal實例,但是它們通過ThreadLocalget/set方法讀寫的并不是同一個實例瞳收,所以保證了在多線程環(huán)境下的獨立性碉京。

二、源碼

2.1 源碼實現(xiàn)

為了加深對于ThreadLocal的理解螟深,我們來分析一下它的內(nèi)部實現(xiàn)谐宙。ThreadLocal設(shè)計的核心思想就是:每一個Thread維護一個ThreadLocalMapThreadLocalMapkeyThreadLocal界弧,而value就是真正要存儲的Object凡蜻。這種方案設(shè)計的優(yōu)點是:

  • 每個MapEntry數(shù)量變小了搭综,之前是Thread的數(shù)量,現(xiàn)在是ThreadLocal的數(shù)量划栓,能提高性能兑巾。
  • 當(dāng)Thread銷毀之后對應(yīng)的ThreadLocalMap也就隨之銷毀了,能減少內(nèi)存使用量忠荞。

我們先來看一下setget的主要流程:

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null)
                return (T)e.value;
        }
        return 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;
    }

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

    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

    void createMap(Thread t, T firstValue) {
        t.threadLocals = new ThreadLocalMap(this, firstValue);
    }

寫入的流程為:

  • 通過靜態(tài)方法currentThread獲取當(dāng)前執(zhí)行指令的線程蒋歌。
  • 得到該線程的私有成員變量threadLocals,其類型為ThreadLocalMap委煤,如果沒有創(chuàng)建那么就先創(chuàng)建堂油。
  • 通過ThreadLocalMapset方法存入實際的Object,其key值為ThreadLocal實例碧绞。

讀取的流程為:

  • 通過靜態(tài)方法currentThread獲取當(dāng)前執(zhí)行指令的線程府框,然后獲取和該線程關(guān)聯(lián)的ThreadLocalMap
  • ThreadLocal實例為key值,通過ThreadLocalMapgetEntry方法找到Object头遭,如果找到就直接返回寓免;如果沒有找到就調(diào)用setInitialValue方法,該方法會調(diào)用到我們重寫的initialValue來嘗試獲取一個初始值计维。

總結(jié)下來就是:ThreadLocal將一個共用的ThreadLocal靜態(tài)實例作為key袜香,將不同對象的引用保存到不同線程的ThreadLocalMap中,然后在線程執(zhí)行的各處通過這個靜態(tài)ThreadLocal實例的get()方法取得自己線程保存的那個對象鲫惶,避免了將這個對象作為參數(shù)傳遞的麻煩蜈首。

它之所以可保證 多線程環(huán)境下的相互獨立,原因在于:每個線程中都有一個自己的ThreadLocalMap類對象欠母,可以將線程自己的對象保持到其中欢策,線程可以正確的訪問到自己的對象。

當(dāng)然赏淌,這種 獨立性必須要基于一個前提通過set方法存儲的對象并不是多個線程共享的踩寇。如果是共享的,那么多個線程get出來的是同一個是實例六水,仍然會存在多線程問題俺孙。

2.2 ThreadLocalMap

ThreadLocalMapThreadLocal中的一個內(nèi)部類,與HashMap類似掷贾,它也會遇到Hash沖突的問題睛榄,HashMap采用了 鏈地址法 解決沖突,而ThreadLocalMap則采用 開放尋址法 解決沖突想帅。

關(guān)于ThreadLocalMap還有一個疑問场靴,就是它有可能會出現(xiàn)內(nèi)存泄漏,原因是:ThreadLocalMapkey值保存的是ThreadLocal的弱引用,假如ThreadLocal被回收旨剥,那么就會無法通過Key找到Object咧欣,假如線程一直沒有結(jié)束,那么這些Object就永遠不會被回收泞边。

ThreadLocalMap內(nèi)部對于這種情況做了優(yōu)化该押,就是在getEntryset方法查找存儲位置的時候,如果發(fā)現(xiàn)了keynull的槽阵谚,那么會將這些槽中對應(yīng)的Object引用置為null蚕礼。這并不能解決所有問題,對于使用者來說梢什,可以做額外的兩項優(yōu)化操作:

  • 手動調(diào)用ThreadLocalremove函數(shù)奠蹬,刪除不再需要的ThreadLocal
  • ThreadLocal聲明為private static的,使得ThreadLocal的生命周期更長嗡午。

參考文獻

(1) 正確理解 ThreadLocal
(2) 深入剖析 ThreadLocal 實現(xiàn)原理以及內(nèi)存泄漏問題
(3) ThreadLocal 和 synchronized 的區(qū)別

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末囤躁,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子荔睹,更是在濱河造成了極大的恐慌狸演,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,639評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僻他,死亡現(xiàn)場離奇詭異宵距,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來献联,“玉大人,你說我怎么就攤上這事哨鸭。” “怎么了娇妓?”我有些...
    開封第一講書人閱讀 157,221評論 0 348
  • 文/不壞的土叔 我叫張陵像鸡,是天一觀的道長。 經(jīng)常有香客問我哈恰,道長坟桅,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,474評論 1 283
  • 正文 為了忘掉前任蕊蝗,我火速辦了婚禮,結(jié)果婚禮上赖舟,老公的妹妹穿的比我還像新娘蓬戚。我一直安慰自己,他們只是感情好宾抓,可當(dāng)我...
    茶點故事閱讀 65,570評論 6 386
  • 文/花漫 我一把揭開白布子漩。 她就那樣靜靜地躺著豫喧,像睡著了一般。 火紅的嫁衣襯著肌膚如雪幢泼。 梳的紋絲不亂的頭發(fā)上紧显,一...
    開封第一講書人閱讀 49,816評論 1 290
  • 那天,我揣著相機與錄音缕棵,去河邊找鬼孵班。 笑死,一個胖子當(dāng)著我的面吹牛招驴,可吹牛的內(nèi)容都是我干的篙程。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼别厘,長吁一口氣:“原來是場噩夢啊……” “哼虱饿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起触趴,我...
    開封第一講書人閱讀 37,718評論 0 266
  • 序言:老撾萬榮一對情侶失蹤氮发,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冗懦,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體爽冕,經(jīng)...
    沈念sama閱讀 44,176評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,511評論 2 327
  • 正文 我和宋清朗相戀三年批狐,在試婚紗的時候發(fā)現(xiàn)自己被綠了扇售。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,646評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡嚣艇,死狀恐怖承冰,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情食零,我是刑警寧澤困乒,帶...
    沈念sama閱讀 34,322評論 4 330
  • 正文 年R本政府宣布,位于F島的核電站贰谣,受9級特大地震影響娜搂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜吱抚,卻給世界環(huán)境...
    茶點故事閱讀 39,934評論 3 313
  • 文/蒙蒙 一百宇、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧秘豹,春花似錦携御、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,755評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涮坐。三九已至,卻和暖如春誓军,著一層夾襖步出監(jiān)牢的瞬間袱讹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,987評論 1 266
  • 我被黑心中介騙來泰國打工昵时, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留捷雕,地道東北人。 一個月前我還...
    沈念sama閱讀 46,358評論 2 360
  • 正文 我出身青樓债查,卻偏偏與公主長得像非区,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子盹廷,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,514評論 2 348

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