Netty_ThreadLocal和FastThreadLocal詳解

在平常開(kāi)發(fā)的時(shí)候擅笔,經(jīng)常使用到線程本地變量磨德,這種類型的變量會(huì)在每個(gè)線程中都有一份,互相不會(huì)產(chǎn)生影響剂公,這樣來(lái)解決多線程并發(fā)問(wèn)題希俩。
那么是如何實(shí)現(xiàn)的呢?

一. ThreadLocal<T>

1.1 例子

   private static final ThreadLocal<AtomicInteger> threadLocal = new ThreadLocal<AtomicInteger>(){
        @Override
        protected AtomicInteger initialValue() {
            AtomicInteger result = new AtomicInteger(0);
            System.out.println("創(chuàng)建 AtomicInteger("+result.hashCode()+")   thread:"+Thread.currentThread().getName());
            return result;
        }
    };

    public static void main(String[] args) {

        for (int index = 0; index < 2; index++) {
            new Thread(() -> {
               for (int i = 0; i < 5; i++) {
                   System.out.println(threadLocal.get().incrementAndGet()
                           +"   thread:"+Thread.currentThread().getName());
               }
            }).start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

運(yùn)行結(jié)果

創(chuàng)建 AtomicInteger(952204834)   thread:Thread-0
創(chuàng)建 AtomicInteger(471787139)   thread:Thread-1
1   thread:Thread-0
2   thread:Thread-0
3   thread:Thread-0
4   thread:Thread-0
1   thread:Thread-1
5   thread:Thread-0
2   thread:Thread-1
3   thread:Thread-1
4   thread:Thread-1
5   thread:Thread-1

從運(yùn)行結(jié)果可以得出每個(gè)線程都創(chuàng)建了AtomicInteger 實(shí)例纲辽,因此彼此不會(huì)產(chǎn)生影響颜武。

ThreadLocal<T> 可以看出兩部分:

  • 一個(gè)是ThreadLocal 對(duì)象實(shí)例(即例子中的 threadLocal)璃搜,這個(gè)實(shí)例只有一個(gè),多線程共享的盒刚。
  • 另一個(gè)是由ThreadLocal 對(duì)象實(shí)例創(chuàng)建的對(duì)象(即例子中的AtomicInteger)腺劣,這個(gè)是每個(gè)線程都會(huì)創(chuàng)建并持有。

因此你會(huì)發(fā)現(xiàn):

  • 每個(gè)線程可以根據(jù)ThreadLocal 對(duì)象實(shí)例threadLocal來(lái)查找對(duì)應(yīng)的所創(chuàng)建的對(duì)象AtomicInteger因块,相當(dāng)于key->value 的鍵值映射關(guān)系橘原。
  • 而每個(gè)線程可以有多個(gè)ThreadLocal 對(duì)象實(shí)例,即多個(gè)key涡上。
  • 那么我們可以斷定趾断,每個(gè)線程肯定有一個(gè)集合對(duì)象來(lái)存儲(chǔ)上面的多個(gè)key->value 鍵值映射關(guān)系,其實(shí)就是 Thread 中成員屬性 ThreadLocal.ThreadLocalMap threadLocals吩愧。

1.2 get 方法

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

    public T get() {
        // 先獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 從當(dāng)前線程中獲取存儲(chǔ)鍵值映射關(guān)系的Map
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null) {
            // 如果這個(gè)Map存在芋酌,那么直接從里面獲取映射關(guān)系e
            ThreadLocal.ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                // 映射關(guān)系e 存在,那么直接獲取創(chuàng)建的對(duì)象
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        // 程序走到這里雁佳,說(shuō)明當(dāng)前線程 這個(gè)ThreadLocal 實(shí)例對(duì)應(yīng)對(duì)象還沒(méi)有創(chuàng)建,
        // 那么就進(jìn)行初始化創(chuàng)建
        return setInitialValue();
    }

從當(dāng)前線程存儲(chǔ)的映射關(guān)系集合 threadLocals 中糖权,查找當(dāng)前這個(gè)ThreadLocal 對(duì)象實(shí)例所對(duì)應(yīng)的對(duì)象是否存在堵腹;存在就返回,不存在就setInitialValue() 方法進(jìn)行創(chuàng)建星澳。

1.3 setInitialValue() 方法

    private T setInitialValue() {
        // 調(diào)用 initialValue() 方法得到初始化值
        T value = initialValue();
        // 先獲取當(dāng)前線程
        Thread t = Thread.currentThread();
        // 從當(dāng)前線程中獲取存儲(chǔ)鍵值映射關(guān)系的Map
        ThreadLocal.ThreadLocalMap map = getMap(t);
        if (map != null)
            // 存儲(chǔ) key-value 的映射關(guān)系
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

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

1.4 ThreadLocalMap

ThreadLocalMap也是用一個(gè)哈希表數(shù)據(jù)結(jié)構(gòu)來(lái)儲(chǔ)存key-value 的映射關(guān)系疚顷,只不過(guò)它不是用鏈地址法來(lái)解決哈希沖突,而是用開(kāi)放地址法的 線性探測(cè)來(lái)解決哈希沖突禁偎。

關(guān)于哈希表腿堤,以及鏈地址法和開(kāi)放地址法的原理,在我的這篇文章哈希表 中有全面的介紹如暖。

1.5 小結(jié)

ThreadLocal.png

從圖中我們就可以知道笆檀,每個(gè)線程中都一個(gè)threadLocals 屬性,它的類型是 ThreadLocalMap, 這個(gè) ThreadLocalMap 會(huì)記錄當(dāng)前線程所有產(chǎn)生的 ThreadLocal 對(duì)象装处。

二. FastThreadLocal<V>

 private static final FastThreadLocal<AtomicInteger> fastThreadLocal = new FastThreadLocal<AtomicInteger>(){
        @Override
        protected AtomicInteger initialValue() {
            AtomicInteger result = new AtomicInteger(0);
            System.out.println("創(chuàng)建 AtomicInteger("+result.hashCode()+")   thread:"+Thread.currentThread().getName());
            return result;
        }
    };

    public static void main(String[] args) {
        for (int index = 0; index < 2; index++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println(fastThreadLocal.get().incrementAndGet()
                                +"   thread:"+Thread.currentThread().getName());
                    }
                }
            }).start();
        }

        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

運(yùn)行結(jié)果

創(chuàng)建 AtomicInteger(125165235)   thread:Thread-1
創(chuàng)建 AtomicInteger(1223947416)   thread:Thread-0
1   thread:Thread-1
1   thread:Thread-0
2   thread:Thread-1
2   thread:Thread-0
3   thread:Thread-1
3   thread:Thread-0
4   thread:Thread-1
5   thread:Thread-1
4   thread:Thread-0
5   thread:Thread-0

從運(yùn)行結(jié)果來(lái)看误债,FastThreadLocal<V>ThreadLocal<T> 效果是一樣的,那么 FastThreadLocal<V> 的優(yōu)點(diǎn)在哪里呢妄迁?

從上面的介紹中寝蹈,我們知道 ThreadLocal<T> 通過(guò)哈希表來(lái)儲(chǔ)存數(shù)據(jù),從哈希表查找數(shù)據(jù)的過(guò)程如下:

  • 根據(jù) ThreadLocal<T> 實(shí)例對(duì)象threadLocal的哈希值登淘,得到對(duì)應(yīng)數(shù)組下標(biāo)箫老。
  • 再比較這個(gè)數(shù)組下標(biāo)存儲(chǔ)的映射關(guān)系entrykey 和實(shí)例對(duì)象threadLocal是否相等,相等的話黔州,就直接返回entryvalue值耍鬓。
  • 如果不等阔籽,那么就要進(jìn)行線性探測(cè),查找下一個(gè)下標(biāo)的映射關(guān)系entry牲蜀,是否符合要求笆制,知道找到映射關(guān)系entrykey和當(dāng)前實(shí)例對(duì)象threadLocal相等。
  • 所以對(duì)于這種開(kāi)發(fā)地址法的哈希表涣达,極個(gè)別情況下在辆,查找過(guò)程可能會(huì)耗時(shí),要進(jìn)行多次線性探測(cè)度苔。

那么 FastThreadLocal<V> 就采用了空間換時(shí)間的方式加快查找速度匆篓。

  • ThreadLocal<T> VS FastThreadLocal<V>
    • ThreadLocal<T> 有一個(gè)threadLocalHashCode 屬性,在創(chuàng)建的時(shí)候被賦值寇窑,而且是不可變的屬性鸦概,代表當(dāng)前這個(gè) ThreadLocal<T>實(shí)例對(duì)象的哈希值,用來(lái)在哈希表ThreadLocalMap中查找對(duì)應(yīng)的映射關(guān)系甩骏。
    • FastThreadLocal<V> 有一個(gè) index 屬性窗市,在創(chuàng)建的時(shí)候被賦值,而且是不可變的屬性饮笛,這個(gè)值就代表當(dāng)前FastThreadLocal<V>實(shí)例對(duì)象在 InternalThreadLocalMap 實(shí)例的 indexedVariables 的下標(biāo)谨设,通過(guò)這個(gè)下標(biāo)得到FastThreadLocal<V>所創(chuàng)建的當(dāng)前線程對(duì)象。
  • ThreadLocalMapInternalThreadLocalMap
    • ThreadLocalMap 是一個(gè)哈希表缎浇,用來(lái)儲(chǔ)存ThreadLocal<T>實(shí)例對(duì)象和它所創(chuàng)建的當(dāng)前線程對(duì)象的映射關(guān)系,就可以通過(guò)ThreadLocal<T>實(shí)例對(duì)象查找它所創(chuàng)建的當(dāng)前線程對(duì)象赴肚。
    • InternalThreadLocalMap 就是一個(gè)數(shù)組素跺,用來(lái)存儲(chǔ)FastThreadLocal<V>實(shí)例對(duì)象所創(chuàng)建的當(dāng)前線程對(duì)象,不過(guò)存儲(chǔ)這個(gè)值的數(shù)組下標(biāo)就是FastThreadLocal<V>實(shí)例對(duì)象的index 屬性值誉券。
    • InternalThreadLocalMap 數(shù)組下標(biāo)0 這個(gè)位置比較特殊指厌,0 下標(biāo)存儲(chǔ)當(dāng)前線程所有的 FastThreadLocal<V> 對(duì)象實(shí)例,用于當(dāng)前線程銷毀時(shí)踊跟,移除當(dāng)前線程所有的 FastThreadLocal<V> 對(duì)象實(shí)例所創(chuàng)建的當(dāng)前線程對(duì)象踩验。
FastThreadLocal.png

需要注意的點(diǎn):

  • 每個(gè)FastThreadLocal 再創(chuàng)建的時(shí)候,index 屬性就被賦值了商玫,也就是說(shuō)這個(gè) FastThreadLocal 實(shí)例箕憾,在每個(gè)線程獲取的 InternalThreadLocalMap 中的下標(biāo)是一樣的,都是 index拳昌。

    這就導(dǎo)致一個(gè)嚴(yán)重問(wèn)題袭异,如果 FastThreadLocal 實(shí)例較多的話,某一個(gè)線程用到了 index 較大FastThreadLocal 實(shí)例的話炬藤,它必須創(chuàng)建一個(gè)很大的數(shù)組御铃,這樣才能在FastThreadLocal 實(shí)例對(duì)應(yīng)下標(biāo) index 中儲(chǔ)存FastThreadLocal 實(shí)例創(chuàng)建的對(duì)象碴里。

  • ThreadLocal 沒(méi)有這個(gè)問(wèn)題,雖然它的哈希值也是創(chuàng)建的時(shí)候就確定了上真,但是它通過(guò) 哈希的方法尋找數(shù)組下標(biāo)咬腋,那么當(dāng)前線程中ThreadLocalMap 的數(shù)組長(zhǎng)度只會(huì)和當(dāng)前線程擁有的ThreadLocal 實(shí)例有關(guān)。

    這個(gè)的問(wèn)題就是通過(guò)哈希查找睡互,效率有點(diǎn)影響根竿。

  • InternalThreadLocalMap0 下標(biāo)做了特殊處理,用來(lái)存放每個(gè)線程擁有的 FastThreadLocal 實(shí)例集合湃缎,當(dāng)線程退出時(shí)犀填,清理這些 FastThreadLocal 實(shí)例為當(dāng)前線程中產(chǎn)生的對(duì)象。
  • ThreadLocalMap 沒(méi)有做這方面的處理嗓违,那是因?yàn)?ThreadLocalMap 中使用 WeakReference<ThreadLocal<?>> 來(lái)記錄 ThreadLocal<?> 實(shí)例的九巡。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市蹂季,隨后出現(xiàn)的幾起案子冕广,更是在濱河造成了極大的恐慌,老刑警劉巖偿洁,帶你破解...
    沈念sama閱讀 211,639評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件撒汉,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡涕滋,警方通過(guò)查閱死者的電腦和手機(jī)睬辐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,277評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)宾肺,“玉大人溯饵,你說(shuō)我怎么就攤上這事∠怯茫” “怎么了丰刊?”我有些...
    開(kāi)封第一講書(shū)人閱讀 157,221評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)增拥。 經(jīng)常有香客問(wèn)我啄巧,道長(zhǎng),這世上最難降的妖魔是什么掌栅? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 56,474評(píng)論 1 283
  • 正文 為了忘掉前任秩仆,我火速辦了婚禮,結(jié)果婚禮上渣玲,老公的妹妹穿的比我還像新娘逗概。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,570評(píng)論 6 386
  • 文/花漫 我一把揭開(kāi)白布逾苫。 她就那樣靜靜地躺著卿城,像睡著了一般。 火紅的嫁衣襯著肌膚如雪铅搓。 梳的紋絲不亂的頭發(fā)上瑟押,一...
    開(kāi)封第一講書(shū)人閱讀 49,816評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音星掰,去河邊找鬼多望。 笑死,一個(gè)胖子當(dāng)著我的面吹牛氢烘,可吹牛的內(nèi)容都是我干的怀偷。 我是一名探鬼主播,決...
    沈念sama閱讀 38,957評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼播玖,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼椎工!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蜀踏,我...
    開(kāi)封第一講書(shū)人閱讀 37,718評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤维蒙,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后果覆,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體颅痊,經(jīng)...
    沈念sama閱讀 44,176評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,511評(píng)論 2 327
  • 正文 我和宋清朗相戀三年局待,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了斑响。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,646評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡钳榨,死狀恐怖恋捆,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情重绷,我是刑警寧澤,帶...
    沈念sama閱讀 34,322評(píng)論 4 330
  • 正文 年R本政府宣布膜毁,位于F島的核電站昭卓,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏瘟滨。R本人自食惡果不足惜候醒,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,934評(píng)論 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望杂瘸。 院中可真熱鬧倒淫,春花似錦、人聲如沸败玉。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,755評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至返干,卻和暖如春兴枯,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背矩欠。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,987評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工财剖, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人癌淮。 一個(gè)月前我還...
    沈念sama閱讀 46,358評(píng)論 2 360
  • 正文 我出身青樓躺坟,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親乳蓄。 傳聞我的和親對(duì)象是個(gè)殘疾皇子咪橙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,514評(píng)論 2 348

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