ThreadLocal總結(jié)

ThreadLocal是java.lang包里的一個(gè)優(yōu)秀的多線程工具剧防。ThreadLocal為變量在每個(gè)線程中都創(chuàng)建了一個(gè)副本,每個(gè)線程可以訪問自己內(nèi)部的副本變量,保證線程的安全狠持。另一方面ThreadLocal也可以作為閱讀源碼的起點(diǎn),代碼量不多瞻润,但是設(shè)計(jì)的十分巧妙喘垂。

ThreadLocal原理

每個(gè)線程Thread持有一個(gè)變量ThreadLocalMap,Map的key就是ThreadLocal變量绍撞,value則是我們要存儲(chǔ)的值正勒。
使用threadLocal.set()方法儲(chǔ)值時(shí),首先通過Thread.currentThread()方法得到當(dāng)前的線程傻铣,然后拿到當(dāng)前線程持有的ThreadLocalMap章贞,將鍵值對(duì)存在ThreadLocalMap中。
使用threadLocal.get()方法取值時(shí)非洲,首先通過Thread.currentThread()方法得到當(dāng)前的線程鸭限,然后拿到當(dāng)前線程持有的ThreadLocalMap蜕径,根據(jù)threadLocal作為key查找對(duì)應(yīng)的value,如果沒取到會(huì)set一個(gè)value為默認(rèn)值的鍵值對(duì)败京,并將默認(rèn)值返回兜喻。
變量值實(shí)際上存儲(chǔ)在線程Thread中的ThreadLocalMap中,因?yàn)槊總€(gè)線程都有屬于自己的ThreadLocalMap喧枷,每個(gè)線程也只能操作屬于自己的ThreadLocalMap虹统,這就既保障了變量的線程安全,又為一些特殊的場(chǎng)景提供了一個(gè)有力的工具隧甚。

ThreadLocalMap原理

上面說到鍵值對(duì)都存在了ThreadLocalMap中车荔,這是一個(gè)自己實(shí)現(xiàn)的類似于HashMap的集合。ThreadLocalMap使用內(nèi)部靜態(tài)類Entry來儲(chǔ)值戚扳,Entry包含了對(duì)ThreadLocal的弱引用以及對(duì)值的強(qiáng)引用忧便。這里為什么對(duì)Key使用弱引用,在下面會(huì)講帽借。ThreadLocalMap用哈希算法維護(hù)一個(gè)Entry數(shù)組用以存儲(chǔ)Entry珠增,用開放地址法來解決哈希沖突。
每個(gè)ThreadLocal對(duì)象都有一個(gè)屬于自己的threadLocalHashCode砍艾,這個(gè)值由靜態(tài)AtomicInteger變量nextHashCode每次加0x61c88647來維護(hù)蒂教,保證每個(gè)ThreadLocal的threadLocalHashCode值不同,并且最大程度下不會(huì)產(chǎn)生哈希碰撞脆荷。在get凝垛、set方法中,ThreadLocalMap使用threadLocalHashCode與數(shù)組的大小減一進(jìn)行與運(yùn)算蜓谋,求得ThreadLocal在數(shù)組中的位置梦皮,如果此時(shí)產(chǎn)生了哈希沖突則遍歷數(shù)組后面的位置一邊清理ThreadLocalMap中key為null的鍵值對(duì),一邊找到空位儲(chǔ)值或從位置中取值桃焕。
這里引申一點(diǎn)剑肯,這種線性探測(cè)的開放地址法在性能上有些差,當(dāng)然這么設(shè)計(jì)有一方面是考慮要順便清理廢棄的鍵值對(duì)观堂。因此Netty自己實(shí)現(xiàn)了一個(gè)FastThreadLocal來提升性能让网,有機(jī)會(huì)在以后會(huì)介紹一下FastThreadLocal的實(shí)現(xiàn)。

ThreadLocal線程泄露問題

上面說到了ThreadLocalMap是線程Thread持有的一個(gè)變量师痕,而ThreadLocalMap中存放著作為key的ThreadLocal對(duì)象和作為value的我們存放的變量寂祥。需要注意的是在其他引用都是強(qiáng)引用的情況下,ThreadLocalMap對(duì)ThreadLocal的引用則是弱引用七兜。這使得ThreadLocal在失去其他強(qiáng)引用時(shí)會(huì)被jvm回收掉,但是鍵值對(duì)中的value還保持著ThreadLocalMap本身的強(qiáng)引用不會(huì)被回收福扬。這些value會(huì)隨著ThreadLocalMap一直活到Thread的生命周期結(jié)束腕铸,也就是會(huì)造成一定意義上的內(nèi)存泄露問題惜犀。
為了一定程度上的解決這個(gè)問題,ThreadLocalMap提供的set狠裹、get虽界、remove方法都會(huì)清理ThreadLocalMap中key為null的鍵值對(duì)。但是如果線程一直存活且不調(diào)用這些方法還是會(huì)產(chǎn)生泄露問題涛菠,因此使用完變量后最好將其remove掉莉御。
如果ThreadLocalMap使用強(qiáng)引用引用ThreadLocal會(huì)造成更嚴(yán)重的內(nèi)存泄露問題。ThreadLocal在失去其他強(qiáng)引用時(shí)因?yàn)門hreadLocalMap對(duì)它一直保持著強(qiáng)引用而無法被回收掉俗冻。ThreadLocalMap也無法簡(jiǎn)單地判斷哪個(gè)鍵值對(duì)是需要被回收的礁叔,只好引用著所有鍵值對(duì)直到線程生命周期結(jié)束后一起被回收。
這里引申一下Tomcat迄薄。Tomcat的Server組件中有一個(gè)ThreadLocalLeakPreventionListener監(jiān)聽器專門用來處理ThreadLocal可能造成的內(nèi)存泄露問題琅关。Tomcat的context重載時(shí)采用的方法是重新初始化一個(gè)Webclassloader類加載器。當(dāng)context重載的時(shí)候讥蔽,如果正在執(zhí)行的線程引用了threadlocal中的對(duì)象涣易,而該對(duì)象由Webclassloader加載,會(huì)造成整個(gè)webclassloader回收不了冶伞,從而造成內(nèi)存泄露新症。Tomcat給出的解決方法是重載時(shí)把線程池中的所有線程銷毀且重新創(chuàng)建,這樣就不會(huì)有泄露的問題了响禽。

InheritableThreadLocal

InheritableThreadLocal是ThreadLocal的子類徒爹,繼承了ThreadLocal的所有使用方法。Thread中其實(shí)持有了兩個(gè)ThreadLocalMap金抡,一個(gè)比較常見用來存放ThreadLocal瀑焦,另一個(gè)則用來存放InheritableThreadLocal。InheritableThreadLocal的特殊之處在于父線程新建線程時(shí)會(huì)將所有ThreadLocalMap中的InheritableThreadLocal繼承給子線程梗肝。子線程在初始化時(shí)就會(huì)將父線程的所有InheritableThreadLocal鍵值對(duì)復(fù)制到子線程中榛瓮,需要注意的是父子線程鍵值對(duì)的value引用的是同一個(gè)對(duì)象(相當(dāng)于淺度拷貝),使用時(shí)需要考慮線程安全問題巫击。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末禀晓,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坝锰,更是在濱河造成了極大的恐慌粹懒,老刑警劉巖,帶你破解...
    沈念sama閱讀 217,734評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件顷级,死亡現(xiàn)場(chǎng)離奇詭異凫乖,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,931評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門帽芽,熙熙樓的掌柜王于貴愁眉苦臉地迎上來删掀,“玉大人,你說我怎么就攤上這事导街∨幔” “怎么了?”我有些...
    開封第一講書人閱讀 164,133評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵搬瑰,是天一觀的道長(zhǎng)款票。 經(jīng)常有香客問我,道長(zhǎng)泽论,這世上最難降的妖魔是什么艾少? 我笑而不...
    開封第一講書人閱讀 58,532評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮佩厚,結(jié)果婚禮上姆钉,老公的妹妹穿的比我還像新娘。我一直安慰自己抄瓦,他們只是感情好潮瓶,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,585評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著钙姊,像睡著了一般毯辅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上煞额,一...
    開封第一講書人閱讀 51,462評(píng)論 1 302
  • 那天思恐,我揣著相機(jī)與錄音,去河邊找鬼膊毁。 笑死胀莹,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的婚温。 我是一名探鬼主播描焰,決...
    沈念sama閱讀 40,262評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼栅螟!你這毒婦竟也來了荆秦?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,153評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤力图,失蹤者是張志新(化名)和其女友劉穎步绸,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吃媒,經(jīng)...
    沈念sama閱讀 45,587評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡瓤介,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,792評(píng)論 3 336
  • 正文 我和宋清朗相戀三年吕喘,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片刑桑。...
    茶點(diǎn)故事閱讀 39,919評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡兽泄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出漾月,到底是詐尸還是另有隱情,我是刑警寧澤胃珍,帶...
    沈念sama閱讀 35,635評(píng)論 5 345
  • 正文 年R本政府宣布梁肿,位于F島的核電站,受9級(jí)特大地震影響觅彰,放射性物質(zhì)發(fā)生泄漏吩蔑。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,237評(píng)論 3 329
  • 文/蒙蒙 一填抬、第九天 我趴在偏房一處隱蔽的房頂上張望烛芬。 院中可真熱鬧,春花似錦飒责、人聲如沸赘娄。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,855評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽遣臼。三九已至,卻和暖如春拾并,著一層夾襖步出監(jiān)牢的瞬間揍堰,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,983評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工嗅义, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留屏歹,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,048評(píng)論 3 370
  • 正文 我出身青樓之碗,卻偏偏與公主長(zhǎng)得像蝙眶,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子继控,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,864評(píng)論 2 354