1. 程式人生 > >Java多執行緒之隔離技術ThreadLocal原始碼詳解

Java多執行緒之隔離技術ThreadLocal原始碼詳解

本篇文章是對ThreadLocal和InheritableThreadLocal,TransmittableThreadLocal的原理和原始碼進行深入分析,並舉例講解,其中前兩個是JDK自帶的。原理相對比較簡單,其解決了單執行緒環境和在單執行緒中又建立執行緒(父子執行緒)中執行緒隔離的問題, TransmittableThreadLocal主要是解決,執行緒池中執行緒複用的場景。全文涉及到原始碼比較多閱讀起來需要動腦筋思考,文章前半部分比較簡單,後半部分比較困難,注意看程式碼註釋。有不懂的可以留言。

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

目錄

  1. 使用原理簡介
  2. 根據JDK原理,自己實現一個
  3. 單執行緒隔離
  4. 父子執行緒隔離
  5. 執行緒池執行緒複用隔離
  6. 丟擲問題和總結,讓你更深入瞭解細節

1. ThreadLocal 的原理是什麼呢 ?

其實就相當於一個Map集合,只不過這個Map 的Key是固定的,都是當前執行緒。
它能解決什麼問題呢? 它存在的價值是什麼呢?

  • 它的存在就是為了執行緒隔離,讓每個執行緒都能擁有屬於自己的變數空間,執行緒之間互相不影響,為什麼這麼說呢? 看下程式碼就明白

通過上面的程式碼,可以發現其實ThreadLocal的set()方法就相當於

之所以能起到執行緒隔離的作用,是因為Key就是當前的執行緒,所以每個執行緒的值都是隔離的,就像上圖那樣。

其實並不是這樣簡單,之所以這樣講是為了,大家理解,其實這裡的核心點在getMap中,從Thread中拿到一個Map,然後把value放到這個執行緒的map中

因為每個執行緒都有一個自己的Map,也就是threadLocals。從而起到了執行緒隔離的作用


2. 根據JDK原理,自己實現一個類似的

測試用例

Result:

3. 單執行緒隔離

什麼是單執行緒隔離,這個是小編自己想的名字,其實是為了和父子執行緒區分開來,上面我們演示的都是屬於在單一執行緒的情況下的使用。

4.父子執行緒隔離

什麼是父子執行緒,需要解釋下是,當我們建立一個執行緒,線上程內有去執行另一個執行緒的時候,作為子執行緒,如何去拿到父執行緒的私有屬性呢?


我們怎麼能拿到父執行緒的屬性呢?

  • 我們看前面標記的①,在get()時候有一個getMap(),在②有一個createMap方法

既然我們想拿到父執行緒的私有變數,那我們想線上程內建立執行緒時候,子執行緒能不能拿到父執行緒的的私有變數呢?

答案:當然是可以的,

我們看Thread的原始碼的時候,可以找到這樣兩個屬性


那麼它是如何實現繼承的呢?我們可以在Thread的構造初始化init方法中,找到答案

看到這裡我們分析,為什麼ThreadLocal不能把父執行緒的私有變數傳遞給子執行緒?

  1. 因為getMap和createMap都是對threadLocals進行操作,而threadLocals變數是不能被繼承的。

那麼我們怎麼去實現能傳遞呢?

其實JDK是為我們實現了一套的,這個類就是InheritableThreadLocal,我們看他為什麼能實現呢? 在看程式碼前,我們先自己思考下,是不是InheritableThreadLocal操作的是可繼承的欄位inheritableThreadLocals呢?答案也是肯定的

在父執行緒內建立子執行緒的時候,子執行緒會在拿到父執行緒中的可繼承的私有變數空間屬性,也就是inheritableThreadLocals欄位。

測試用例


5. 執行緒池執行緒複用隔離

在解決上面的問題後,我們來研究一個更有難度的問題,就是執行緒池執行緒複用的情況,怎麼實現?

為什麼會遇到這個問題呢? 是因為線上程池中核心執行緒用完,並不會直接被回收,而是返回到執行緒池中,既然是重新利用,

那麼久不會重新建立執行緒,不會建立執行緒,父子之間就不會傳遞(如果這點沒有明白,請繼續看上面父子執行緒)。

那麼這時父子執行緒關係的ThreadLocal值傳遞已經沒有意義。

那麼根據這個原理 ,我們繼續來深入研究一波。


解決方案是什麼呢?

在submit的時候把父執行緒copy給子執行緒

在execute的時候結束後吧執行緒的ThreadLocal清理,就能解決這個問題

上面是網上搜到的答案,小編在證實上面答案的時候走了很多坑,根本沒有找到清理的程式碼。最後小編髮現,根本就沒有清理的程式碼,而是重新賦值的形式來實現清理。

到底是怎麼來實現的呢?我們看TransmittableThreadLocal核心程式碼

  1. 拿到建立執行緒時候的備份所有執行緒空間 【深複製】因為淺複製會結果會被修改

  2. 在執行時候將之前的備份恢復,將最新的值返回到backup變數中

  3. 執行完成後,再將backup最新的值重新寫入到TransmittableThreadLocal中

程式碼看起來很簡潔,但是理解起來並不容易,每一步都有很多細節?我們一個一個來看

  1. copy方法。

TransmittableThreadLocal內維護了一個holder儲存所有TransmittableThreadLocal例項當set時候addValue方法

如果還沒新增就新增,null在這裡只是佔位,沒有其他用,因為this就包含了所有值

copy方法就是將holder裡面維護的TransmittableThreadLocal例項和值通過深複製的形式返回,為什麼是深複製,因為引用複製可能會在其他地方值被修改。

  1. backupAndSetToCopied方法從copide中恢復資料,然後新值返回出去,放到backup變數中

  2. 當執行緒已經執行完,在呼叫restoreBackup方法恢復backup變數中的值。

這點理解其他優點困難,儘管小編已經很努力的講清楚,但是可以通過下面一個例子可以將以上幾種方法的用處講清。

請注意文中的註釋!

問題

子執行緒修改變數空間值,是否會影響父執行緒值?

答案:當然影響。因為子執行緒獲取父執行緒的inheritableThreadLocals時候,方法ThreadLocal.createInheritedMap(parent.inheritableThreadLocals)其實是淺複製,也就是引用複製,其主要用途是從key.childValue,就是執行ThreadLocal的繼承者,重寫childValue方法,從而能改變父執行緒的本地空間ThreadLocal


交給子類去實現了

總結:

  • ThreadLocal 基礎實現 (原理: 儲存著執行緒中)

  • inheritableThreadLocals 實現了父子直接的傳遞 (原理: 可繼承的變數空間,在Thread初始化init方法時候給子賦值)

  • TransmittableThreadLocal 實現執行緒複用 (原理: 在每次執行緒執行時候重新給ThreadLocal賦值)

好了,時間不早了, 今天的課程到此。 喜歡的童鞋請點選關注,謝謝你的支援。 沒有任何廣告,純粹分析技術,一起共同成長進步 。