ThreadLocal實(shí)戰(zhàn)(一:ThreadLocal)

一:ThreadLocal是什么扁凛?

學(xué)習(xí)JDK的類最好的辦法就是先看一下源碼上的注解


源碼注解


從JAVA官方對ThreadLocal類的說明定義(定義在示例代碼中):ThreadLocal類用來提供線程內(nèi)部的局部變量沽瘦。這種變量在多線程環(huán)境下訪問(通過get和set方法訪問)時(shí)能保證各個(gè)線程的變量相對獨(dú)立于其他線程內(nèi)的變量袍祖。ThreadLocal實(shí)例通常來說都是private static類型的肉康,用于關(guān)聯(lián)線程和線程上下文物咳。

我們可以得知ThreadLocal的作用是:ThreadLocal的作用是提供線程內(nèi)的局部變量憔四,不同的線程之間不會(huì)相互干擾伐憾,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或組件之間一些公共變量的傳遞的復(fù)雜度秒际。

上述可以概述為:ThreadLocal提供線程內(nèi)部的局部變量悬赏,在本線程內(nèi)隨時(shí)隨地可取,隔離其他線程娄徊。


二:ThreadLocal如何使用闽颇?

學(xué)習(xí)一個(gè)JDK工具類,首先是使用它寄锐,再回頭了解源碼和設(shè)計(jì)理念兵多,所以我們先看它是如何去運(yùn)用的。

首先我定義一下這次運(yùn)用所使用的數(shù)據(jù)載體


ThreadLocal存放介質(zhì)

可以看到上圖橄仆,我定義了一個(gè)類剩膘,這個(gè)類就是描述語言環(huán)境的一個(gè)結(jié)構(gòu)體。

在很多RPC或者webservice交互中盆顾,或多或少有國際化的需求怠褐,那么為了減少重復(fù)設(shè)置語言環(huán)境以及方法入?yún)⒃黾诱Z言參數(shù),將國際化語言環(huán)境放入ThreadLocal中是一個(gè)不錯(cuò)的選擇椎扬,它也是代表一個(gè)上下文Transaction的限界惫搏,所以本文的樣例選用這個(gè)類作為ThreadLocalMap的載體具温,另外提供Builder模式進(jìn)行create,覆寫toString進(jìn)行測試蚕涤。

下面看一下ThreadLocal是如何去運(yùn)用的


ThreadLocal運(yùn)用


如上圖,ThreadLocal可以以這種方式去運(yùn)用铣猩,首先定義一個(gè)工具類的概念揖铜,將Context的概念封裝在工具類里。

而存放Context載體的介質(zhì)就是ThreadLocal达皿,他提供initialValue天吓、set、get峦椰、remove等方法龄寞,而工具類中靜態(tài)方法對這些方法做了一層適配,其實(shí)有點(diǎn)類似于代理或者適配汤功,不過這里的目的是使setLanguageContext等方法更明確含義物邑,封裝了底層細(xì)節(jié),對上層模塊友好。

當(dāng)然色解,這里的例子泛型也可以換為String或者其他類型的茂嗓,并且也可以省略初始化方法的覆寫,一切取決于需求科阎,這里只是提供方法并不糾結(jié)具象述吸。

Ok,我們來試一下這個(gè)類的使用效果锣笨。


ThreadLocal測試

可以看到蝌矛,他能夠隔離開線程,使用自己的私有變量错英,第一次循環(huán)和第二次循環(huán)打印出的language字段并不同朴读。


三:ThreadLocal源碼與設(shè)計(jì)思想

上面已經(jīng)介紹了ThreadLocal的作用和運(yùn)用方式,那么在使用完它以后走趋,我們來探尋一下它實(shí)現(xiàn)的原理衅金,和它設(shè)計(jì)的思想是什么,學(xué)習(xí)原理簿煌,知其然知其所以然氮唯,有助于我們避開ThreadLocal的使用誤區(qū),也有助于理解如何選擇什么樣的場景適合ThreadLocal姨伟。

看一下源碼


ThreadLocal源碼

先看一下它的靜態(tài)內(nèi)部類惩琉,ThreadLocalMap

ThreadLocalMap維護(hù)了Entry環(huán)形數(shù)組,數(shù)組中元素Entry的邏輯上的key為某個(gè)ThreadLocal對象(實(shí)際上是指向該ThreadLocal對象的弱引用)夺荒,value為代碼中該線程往該ThreadLoacl變量實(shí)際塞入的值瞒渠。

讀到這里,如果不問不答為什么是這樣的定義形式技扼,為什么要用弱引用伍玖,等于沒讀懂源碼。

因?yàn)槿绻@里使用普通的key-value形式來定義存儲(chǔ)結(jié)構(gòu)剿吻,實(shí)質(zhì)上就會(huì)造成節(jié)點(diǎn)的生命周期與線程強(qiáng)綁定窍箍,只要線程沒有銷毀,那么節(jié)點(diǎn)在GC分析中一直處于可達(dá)狀態(tài)丽旅,沒辦法被回收椰棘,而程序本身也無法判斷是否可以清理節(jié)點(diǎn)。弱引用是Java中四檔引用的第三檔榄笙,比軟引用更加弱一些邪狞,如果一個(gè)對象沒有強(qiáng)引用鏈可達(dá),那么一般活不過下一次GC茅撞。當(dāng)某個(gè)ThreadLocal已經(jīng)沒有強(qiáng)引用可達(dá)帆卓,則隨著它被垃圾回收杆逗,在ThreadLocalMap里對應(yīng)的Entry的鍵值會(huì)失效,這為ThreadLocalMap本身的垃圾清理提供了便利鳞疲。


ThreadLocalMap源碼

上面的這些屬性以及方法罪郊,是Entry的定義。

可以看到rehash的負(fù)載因子是設(shè)置最壞三分之二容量擴(kuò)容尚洽。

而容量默認(rèn)是16悔橄,注解Must be a power of two,為什么一定要是2的次冪腺毫,我理解的原因有兩個(gè)癣疟,第一是ThreadLocalMap使用的是線性探測法,均勻分布的好處在于很快就能探測到下一個(gè)臨近的可用slot潮酒,從而保證效率睛挚。第二是位運(yùn)算比取模效率高,rehash的時(shí)候只需要判斷0還是1急黎。

來看一看關(guān)于ThreadLocal的set方法實(shí)現(xiàn)


ThreadLocal set

這個(gè)代碼再簡單不過了扎狱,不多介紹了,getMap方法的實(shí)現(xiàn)是傳入一個(gè)Thread引用勃教,返回一個(gè)ThreadLocalMap淤击,見下圖


Thread源碼

原來Thread類將ThreadLocalMap作為了一個(gè)全局變量,getMap方法只是拿到這個(gè)變量故源,如果不是空污抬,就放值進(jìn)entry,如果是空绳军,就創(chuàng)建一個(gè)ThreadLocalMap指向當(dāng)前線程threadLocals變量印机。

再看看get方法


ThreadLocal get

還是一樣的流程,先拿到當(dāng)前線程门驾,然后拿到當(dāng)前線程內(nèi)的ThreadLocalMap

重點(diǎn)是getEntry方法射赛。


ThreadLocal getEntry

上圖的table是Object[],其實(shí)就是個(gè)map猎唁,這里的map用的是線性探測解決沖突咒劲,而hashmap是用拉鏈法顷蟆。

調(diào)用getEntryAfterMiss線性探測诫隅,過程中每碰到無效slot,調(diào)用expungeStaleEntry進(jìn)行段清理帐偎;如果找到了key逐纬,則返回結(jié)果entry,所以這里get也有去除一些無效引用的作用削樊,上面的while語句就是個(gè)線性探測豁生。

看一下remove


ThreadLocal remove

remove方法相對于getEntry和set方法比較簡單兔毒,直接在table中找key,如果找到了甸箱,把弱引用斷了做一次段清理育叁。

至此源碼部分就簡單介紹完了,關(guān)于Entry內(nèi)部實(shí)現(xiàn)還有很多沒有詳細(xì)介紹的芍殖,因?yàn)槲矣X得關(guān)于散列還有性能方面設(shè)計(jì)豪嗽,其實(shí)并不是我這次分享主要的目的,主要的目的是介紹另一種線程安全的實(shí)現(xiàn)手段豌骏,是一個(gè)宏觀的概念龟梦,如果讀者自己對于ThreadLocal在性能上,內(nèi)存泄漏上窃躲,斐波那契散列等內(nèi)容感興趣计贰,可以自己去仔細(xì)閱讀源碼。

最后總結(jié)一下ThreadLocal的設(shè)計(jì)理念蒂窒。

首先堆內(nèi)存是共享的躁倒,那么必須共享堆的時(shí)候,有加鎖和不變性等方式去解決數(shù)據(jù)共享問題洒琢。

還有還有以空間換時(shí)間的思路樱溉,ThreadLocal就屬于后者。


堆棧圖


項(xiàng)目代碼:https://github.com/Spring5945/Concurrent

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末纬凤,一起剝皮案震驚了整個(gè)濱河市福贞,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌停士,老刑警劉巖挖帘,帶你破解...
    沈念sama閱讀 212,222評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異恋技,居然都是意外死亡拇舀,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,455評論 3 385
  • 文/潘曉璐 我一進(jìn)店門蜻底,熙熙樓的掌柜王于貴愁眉苦臉地迎上來骄崩,“玉大人,你說我怎么就攤上這事薄辅∫鳎” “怎么了?”我有些...
    開封第一講書人閱讀 157,720評論 0 348
  • 文/不壞的土叔 我叫張陵站楚,是天一觀的道長脱惰。 經(jīng)常有香客問我,道長窿春,這世上最難降的妖魔是什么拉一? 我笑而不...
    開封第一講書人閱讀 56,568評論 1 284
  • 正文 為了忘掉前任采盒,我火速辦了婚禮,結(jié)果婚禮上蔚润,老公的妹妹穿的比我還像新娘磅氨。我一直安慰自己,他們只是感情好嫡纠,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,696評論 6 386
  • 文/花漫 我一把揭開白布悍赢。 她就那樣靜靜地躺著,像睡著了一般货徙。 火紅的嫁衣襯著肌膚如雪左权。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,879評論 1 290
  • 那天痴颊,我揣著相機(jī)與錄音赏迟,去河邊找鬼。 笑死蠢棱,一個(gè)胖子當(dāng)著我的面吹牛锌杀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泻仙,決...
    沈念sama閱讀 39,028評論 3 409
  • 文/蒼蘭香墨 我猛地睜開眼糕再,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了玉转?” 一聲冷哼從身側(cè)響起突想,我...
    開封第一講書人閱讀 37,773評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎究抓,沒想到半個(gè)月后猾担,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,220評論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡刺下,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,550評論 2 327
  • 正文 我和宋清朗相戀三年绑嘹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片橘茉。...
    茶點(diǎn)故事閱讀 38,697評論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡工腋,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出畅卓,到底是詐尸還是另有隱情擅腰,我是刑警寧澤,帶...
    沈念sama閱讀 34,360評論 4 332
  • 正文 年R本政府宣布髓介,位于F島的核電站惕鼓,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏唐础。R本人自食惡果不足惜箱歧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,002評論 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望一膨。 院中可真熱鬧呀邢,春花似錦、人聲如沸豹绪。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,782評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽瞒津。三九已至蝉衣,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間巷蚪,已是汗流浹背病毡。 一陣腳步聲響...
    開封第一講書人閱讀 32,010評論 1 266
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屁柏,地道東北人啦膜。 一個(gè)月前我還...
    沈念sama閱讀 46,433評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像淌喻,于是被迫代替她去往敵國和親僧家。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,587評論 2 350

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