多線程(三)- ThreadLocal詳解(轉(zhuǎn))

原文: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筋思考,文章前半部分比較簡單,后半部分比較困難,注意看代碼注釋。有不懂的可以留言。

image.png

以上是百度百科檢索到的描述,相信通過上面的描述大家已經(jīng)有了一個(gè)大概的了解,也相信大多數(shù)開發(fā)人員對這個(gè)類也是比較了解的,小編首先從原理開始講解,開始吧!

目錄

  1. 使用原理簡介
  2. 根據(jù)JDK原理,自己實(shí)現(xiàn)一個(gè)
  3. 單線程隔離
  4. 父子線程隔離
  5. 線程池線程復(fù)用隔離
  6. 拋出問題和總結(jié),讓你更深入了解細(xì)節(jié)

1. ThreadLocal 的原理是什么呢 ?

其實(shí)就相當(dāng)于一個(gè)Map集合,只不過這個(gè)Map 的Key是固定的,都是當(dāng)前線程。 它能解決什么問題呢? 它存在的價(jià)值是什么呢?

  • 它的存在就是為了線程隔離,讓每個(gè)線程都能擁有屬于自己的變量空間,線程之間互相不影響,為什么這么說呢? 看下代碼就明白
image

通過上面的代碼,可以發(fā)現(xiàn)其實(shí)ThreadLocal的set()方法就相當(dāng)于
image

之所以能起到線程隔離的作用,是因?yàn)镵ey就是當(dāng)前的線程,所以每個(gè)線程的值都是隔離的,就像上圖那樣。

其實(shí)并不是這樣簡單,之所以這樣講是為了,大家理解,其實(shí)這里的核心點(diǎn)在getMap中,從Thread中拿到一個(gè)Map,然后把value放到這個(gè)線程的map中

因?yàn)槊總€(gè)線程都有一個(gè)自己的Map,也就是threadLocals。從而起到了線程隔離的作用

image
image

2. 根據(jù)JDK原理,自己實(shí)現(xiàn)一個(gè)類似的

image

測試用例

image

Result:

image

3. 單線程隔離

什么是單線程隔離,這個(gè)是小編自己想的名字,其實(shí)是為了和父子線程區(qū)分開來,上面我們演示的都是屬于在單一線程的情況下的使用。

4.父子線程隔離

什么是父子線程,需要解釋下是,當(dāng)我們創(chuàng)建一個(gè)線程,在線程內(nèi)有去運(yùn)行另一個(gè)線程的時(shí)候,作為子線程,如何去拿到父線程的私有屬性呢?

image
image

我們怎么能拿到父線程的屬性呢?

  • 我們看前面標(biāo)記的①,在get()時(shí)候有一個(gè)getMap(),在②有一個(gè)createMap方法
    image

既然我們想拿到父線程的私有變量,那我們想在線程內(nèi)創(chuàng)建線程時(shí)候,子線程能不能拿到父線程的的私有變量呢?

答案:當(dāng)然是可以的,
我們看Thread的源碼的時(shí)候,可以找到這樣兩個(gè)屬性

image

那么它是如何實(shí)現(xiàn)繼承的呢?我們可以在Thread的構(gòu)造初始化init方法中,找到答案
image

看到這里我們分析,為什么ThreadLocal不能把父線程的私有變量傳遞給子線程?

  1. 因?yàn)間etMap和createMap都是對threadLocals進(jìn)行操作,而threadLocals變量是不能被繼承的。

那么我們怎么去實(shí)現(xiàn)能傳遞呢?

其實(shí)JDK是為我們實(shí)現(xiàn)了一套的,這個(gè)類就是InheritableThreadLocal,我們看他為什么能實(shí)現(xiàn)呢? 在看代碼前,我們先自己思考下,是不是InheritableThreadLocal操作的是可繼承的字段inheritableThreadLocals呢?答案也是肯定的

image

在父線程內(nèi)創(chuàng)建子線程的時(shí)候,子線程會(huì)在拿到父線程中的可繼承的私有變量空間屬性,也就是inheritableThreadLocals字段。

測試用例

image
image

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ù)來深入研究一波。

image
image

解決方案是什么呢?

`在submit的時(shí)候把父線程copy給子線程`
`在execute的時(shí)候結(jié)束后吧線程的ThreadLocal清理,就能解決這個(gè)問題`

上面是網(wǎng)上搜到的答案,小編在證實(shí)上面答案的時(shí)候走了很多坑,根本沒有找到清理的代碼。最后小編發(fā)現(xiàn),根本就沒有清理的代碼,而是重新賦值的形式來實(shí)現(xiàn)清理。

到底是怎么來實(shí)現(xiàn)的呢?我們看TransmittableThreadLocal核心代碼

image
  1. 拿到創(chuàng)建線程時(shí)候的備份所有線程空間 【深復(fù)制】因?yàn)闇\復(fù)制會(huì)結(jié)果會(huì)被修改
  2. 在執(zhí)行時(shí)候?qū)⒅暗膫浞莼謴?fù),將最新的值返回到backup變量中
  3. 執(zhí)行完成后,再將backup最新的值重新寫入到TransmittableThreadLocal中

代碼看起來很簡潔,但是理解起來并不容易,每一步都有很多細(xì)節(jié)?我們一個(gè)一個(gè)來看

  1. copy方法。

TransmittableThreadLocal內(nèi)維護(hù)了一個(gè)holder保存所有TransmittableThreadLocal實(shí)例當(dāng)set時(shí)候addValue方法

image

如果還沒添加就添加,null在這里只是占位,沒有其他用,因?yàn)閠his就包含了所有值
image

copy方法就是將holder里面維護(hù)的TransmittableThreadLocal實(shí)例和值通過深復(fù)制的形式返回,為什么是深復(fù)制,因?yàn)橐脧?fù)制可能會(huì)在其他地方值被修改。

  1. backupAndSetToCopied方法從copide中恢復(fù)數(shù)據(jù),然后新值返回出去,放到backup變量中
  2. 當(dāng)線程已經(jīng)執(zhí)行完,在調(diào)用restoreBackup方法恢復(fù)backup變量中的值。
    這點(diǎn)理解其他優(yōu)點(diǎn)困難,盡管小編已經(jīng)很努力的講清楚,但是可以通過下面一個(gè)例子可以將以上幾種方法的用處講清。

請注意文中的注釋!

image

問題

子線程修改變量空間值,是否會(huì)影響父線程值?

答案:當(dāng)然影響。因?yàn)樽泳€程獲取父線程的inheritableThreadLocals時(shí)候,方法ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)其實(shí)是淺復(fù)制,也就是引用復(fù)制,其主要用途是從key.childValue,就是運(yùn)行ThreadLocal的繼承者,重寫childValue方法,從而能改變父線程的本地空間ThreadLocal

image
image

交給子類去實(shí)現(xiàn)了

總結(jié):

  • ThreadLocal 基礎(chǔ)實(shí)現(xiàn) (原理: 保存著線程中)
  • inheritableThreadLocals 實(shí)現(xiàn)了父子直接的傳遞 (原理: 可繼承的變量空間,在Thread初始化init方法時(shí)候給子賦值)
  • TransmittableThreadLocal 實(shí)現(xiàn)線程復(fù)用 (原理: 在每次線程執(zhí)行時(shí)候重新給ThreadLocal賦值)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 進(jìn)程和線程 進(jìn)程 所有運(yùn)行中的任務(wù)通常對應(yīng)一個(gè)進(jìn)程,當(dāng)一個(gè)程序進(jìn)入內(nèi)存運(yùn)行時(shí),即變成一個(gè)進(jìn)程.進(jìn)程是處于運(yùn)行過程中...
    勝浩_ae28閱讀 5,257評論 0 23
  • 線程池ThreadPoolExecutor corepoolsize:核心池的大小,默認(rèn)情況下,在創(chuàng)建了線程池之后...
    irckwk1閱讀 863評論 0 0
  • ??一個(gè)任務(wù)通常就是一個(gè)程序,每個(gè)運(yùn)行中的程序就是一個(gè)進(jìn)程。當(dāng)一個(gè)程序運(yùn)行時(shí),內(nèi)部可能包含了多個(gè)順序執(zhí)行流,每個(gè)順...
    OmaiMoon閱讀 1,803評論 0 12
  • 編輯文件,加入以下內(nèi)容: 然后執(zhí)行 讓參數(shù)生效。 net.ipv4.tcp_syncookies = 1 表示開啟...
    螢火蟲de夢閱讀 474評論 0 0

友情鏈接更多精彩內(nèi)容