原文:https://mp.weixin.qq.com/s/mo3-y-45_ao54b5T7ez7iA
本篇文章是對ThreadLocal和InheritableThreadLocal,TransmittableThreadLocal的原理和源碼進(jìn)行深入分析,并舉例講解,其中前兩個(gè)是JDK自帶的。原理相對比較簡單,其解決了單線程環(huán)境和在單線程中又創(chuàng)建線程(父子線程)中線程隔離的問題, TransmittableThreadLocal主要是解決,線程池中線程復(fù)用的場景。全文涉及到源碼比較多閱讀起來需要?jiǎng)幽X筋思考,文章前半部分比較簡單,后半部分比較困難,注意看代碼注釋。有不懂的可以留言。

以上是百度百科檢索到的描述,相信通過上面的描述大家已經(jīng)有了一個(gè)大概的了解,也相信大多數(shù)開發(fā)人員對這個(gè)類也是比較了解的,小編首先從原理開始講解,開始吧!
目錄
- 使用原理簡介
- 根據(jù)JDK原理,自己實(shí)現(xiàn)一個(gè)
- 單線程隔離
- 父子線程隔離
- 線程池線程復(fù)用隔離
- 拋出問題和總結(jié),讓你更深入了解細(xì)節(jié)
1. ThreadLocal 的原理是什么呢 ?
其實(shí)就相當(dāng)于一個(gè)Map集合,只不過這個(gè)Map 的Key是固定的,都是當(dāng)前線程。 它能解決什么問題呢? 它存在的價(jià)值是什么呢?
- 它的存在就是為了線程隔離,讓每個(gè)線程都能擁有屬于自己的變量空間,線程之間互相不影響,為什么這么說呢? 看下代碼就明白
之所以能起到線程隔離的作用,是因?yàn)镵ey就是當(dāng)前的線程,所以每個(gè)線程的值都是隔離的,就像上圖那樣。
其實(shí)并不是這樣簡單,之所以這樣講是為了,大家理解,其實(shí)這里的核心點(diǎn)在getMap中,從Thread中拿到一個(gè)Map,然后把value放到這個(gè)線程的map中
因?yàn)槊總€(gè)線程都有一個(gè)自己的Map,也就是threadLocals。從而起到了線程隔離的作用
2. 根據(jù)JDK原理,自己實(shí)現(xiàn)一個(gè)類似的
測試用例
Result:
3. 單線程隔離
什么是單線程隔離,這個(gè)是小編自己想的名字,其實(shí)是為了和父子線程區(qū)分開來,上面我們演示的都是屬于在單一線程的情況下的使用。
4.父子線程隔離
什么是父子線程,需要解釋下是,當(dāng)我們創(chuàng)建一個(gè)線程,在線程內(nèi)有去運(yùn)行另一個(gè)線程的時(shí)候,作為子線程,如何去拿到父線程的私有屬性呢?
我們怎么能拿到父線程的屬性呢?
-
我們看前面標(biāo)記的①,在get()時(shí)候有一個(gè)getMap(),在②有一個(gè)createMap方法 image
既然我們想拿到父線程的私有變量,那我們想在線程內(nèi)創(chuàng)建線程時(shí)候,子線程能不能拿到父線程的的私有變量呢?
答案:當(dāng)然是可以的,
我們看Thread的源碼的時(shí)候,可以找到這樣兩個(gè)屬性
看到這里我們分析,為什么ThreadLocal不能把父線程的私有變量傳遞給子線程?
- 因?yàn)間etMap和createMap都是對threadLocals進(jìn)行操作,而threadLocals變量是不能被繼承的。
那么我們怎么去實(shí)現(xiàn)能傳遞呢?
其實(shí)JDK是為我們實(shí)現(xiàn)了一套的,這個(gè)類就是InheritableThreadLocal,我們看他為什么能實(shí)現(xiàn)呢? 在看代碼前,我們先自己思考下,是不是InheritableThreadLocal操作的是可繼承的字段inheritableThreadLocals呢?答案也是肯定的
在父線程內(nèi)創(chuàng)建子線程的時(shí)候,子線程會(huì)在拿到父線程中的可繼承的私有變量空間屬性,也就是inheritableThreadLocals字段。
測試用例
5. 線程池線程復(fù)用隔離
在解決上面的問題后,我們來研究一個(gè)更有難度的問題,就是線程池線程復(fù)用的情況,怎么實(shí)現(xiàn)?
為什么會(huì)遇到這個(gè)問題呢? 是因?yàn)樵诰€程池中核心線程用完,并不會(huì)直接被回收,而是返回到線程池中,既然是重新利用,
那么久不會(huì)重新創(chuàng)建線程,不會(huì)創(chuàng)建線程,父子之間就不會(huì)傳遞(如果這點(diǎn)沒有明白,請繼續(xù)看上面父子線程)。
那么這時(shí)父子線程關(guān)系的ThreadLocal值傳遞已經(jīng)沒有意義。
那么根據(jù)這個(gè)原理 ,我們繼續(xù)來深入研究一波。
解決方案是什么呢?
`在submit的時(shí)候把父線程copy給子線程`
`在execute的時(shí)候結(jié)束后吧線程的ThreadLocal清理,就能解決這個(gè)問題`
上面是網(wǎng)上搜到的答案,小編在證實(shí)上面答案的時(shí)候走了很多坑,根本沒有找到清理的代碼。最后小編發(fā)現(xiàn),根本就沒有清理的代碼,而是重新賦值的形式來實(shí)現(xiàn)清理。
到底是怎么來實(shí)現(xiàn)的呢?我們看TransmittableThreadLocal核心代碼
- 拿到創(chuàng)建線程時(shí)候的備份所有線程空間 【深復(fù)制】因?yàn)闇\復(fù)制會(huì)結(jié)果會(huì)被修改
- 在執(zhí)行時(shí)候?qū)⒅暗膫浞莼謴?fù),將最新的值返回到backup變量中
- 執(zhí)行完成后,再將backup最新的值重新寫入到TransmittableThreadLocal中
代碼看起來很簡潔,但是理解起來并不容易,每一步都有很多細(xì)節(jié)?我們一個(gè)一個(gè)來看
- copy方法。
TransmittableThreadLocal內(nèi)維護(hù)了一個(gè)holder保存所有TransmittableThreadLocal實(shí)例當(dāng)set時(shí)候addValue方法
copy方法就是將holder里面維護(hù)的TransmittableThreadLocal實(shí)例和值通過深復(fù)制的形式返回,為什么是深復(fù)制,因?yàn)橐脧?fù)制可能會(huì)在其他地方值被修改。
- backupAndSetToCopied方法從copide中恢復(fù)數(shù)據(jù),然后新值返回出去,放到backup變量中
- 當(dāng)線程已經(jīng)執(zhí)行完,在調(diào)用restoreBackup方法恢復(fù)backup變量中的值。
這點(diǎn)理解其他優(yōu)點(diǎn)困難,盡管小編已經(jīng)很努力的講清楚,但是可以通過下面一個(gè)例子可以將以上幾種方法的用處講清。
請注意文中的注釋!
問題
子線程修改變量空間值,是否會(huì)影響父線程值?
答案:當(dāng)然影響。因?yàn)樽泳€程獲取父線程的inheritableThreadLocals時(shí)候,方法ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)其實(shí)是淺復(fù)制,也就是引用復(fù)制,其主要用途是從key.childValue,就是運(yùn)行ThreadLocal的繼承者,重寫childValue方法,從而能改變父線程的本地空間ThreadLocal
交給子類去實(shí)現(xiàn)了
總結(jié):
- ThreadLocal 基礎(chǔ)實(shí)現(xiàn) (原理: 保存著線程中)
- inheritableThreadLocals 實(shí)現(xiàn)了父子直接的傳遞 (原理: 可繼承的變量空間,在Thread初始化init方法時(shí)候給子賦值)
- TransmittableThreadLocal 實(shí)現(xiàn)線程復(fù)用 (原理: 在每次線程執(zhí)行時(shí)候重新給ThreadLocal賦值)