深入淺出 ThreadLocal

佔小狼發表於2016-08-18

前言

ThreadLocal為變數在每個執行緒中都建立了一個副本,所以每個執行緒可以訪問自己內部的副本變數,不同執行緒之間不會互相干擾。本文會基於實際場景介紹ThreadLocal如何使用以及內部實現機制。

應用場景

最近的一個web專案中,由於Parameter物件的資料需要在多個模組中使用,如果採用引數傳遞的方式,顯然會增加模組之間的耦合性。先看看用ThreadLocal是如何實現模組間共享資料的。

  1. 在模組A中通過Parameter.init初始化。
  2. 在模組B或模組C中通過Parameter.get方法可以獲得同一執行緒中模組A已經初始化的Parameter物件。

那麼,在什麼場景下比較適合使用ThreadLocal?stackoverflow上有人給出了還不錯的回答。
When and how should I use a ThreadLocal variable?
One possible (and common) use is when you have some object that is not thread-safe, but you want to avoid synchronizing access to that object (I’m looking at you, SimpleDateFormat). Instead, give each thread its own instance of the object.

實現原理

從執行緒Thread的角度來看,每個執行緒內部都會持有一個對ThreadLocalMap例項的引用,ThreadLocalMap例項相當於執行緒的區域性變數空間,儲存著執行緒各自的資料,具體如下:

深入淺出 ThreadLocal

ThreadLocal.png

Entry

Entry繼承自WeakReference類,是儲存執行緒私有變數的資料結構。ThreadLocal例項作為引用,意味著如果ThreadLocal例項為null,就可以從table中刪除對應的Entry。

ThreadLocalMap

內部使用table陣列儲存Entry,預設大小INITIAL_CAPACITY(16),先介紹幾個引數:

  • size:table中元素的數量。
  • threshold:table大小的2/3,當size >= threshold時,遍歷table並刪除key為null的元素,如果刪除後size >= threshold*3/4時,需要對table進行擴容。

ThreadLocal.set() 實現

從上面程式碼中看出來:

  1. 從當前執行緒Thread中獲取ThreadLocalMap例項。
  2. ThreadLocal例項和value封裝成Entry。

接下去看看Entry存入table陣列如何實現的:

  1. 通過ThreadLocal的nextHashCode方法生成hash值。

    從nextHashCode方法可以看出,ThreadLocal每例項化一次,其hash值就原子增加HASH_INCREMENT。
  2. 通過 hash & (len -1) 定位到table的位置i,假設table中i位置的元素為f。
  3. 如果f != null,假設f中的引用為k:
    • 如果k和當前ThreadLocal例項一致,則修改value值,返回。
    • 如果k為null,說明這個f已經是stale(陳舊的)的元素。呼叫replaceStaleEntry方法刪除table中所有陳舊的元素(即entry的引用為null)並插入新元素,返回。
    • 否則通過nextIndex方法找到下一個元素f,繼續進行步驟3。
  4. 如果f == null,則把Entry加入到table的i位置中。
  5. 通過cleanSomeSlots刪除陳舊的元素,如果table中沒有元素刪除,需判斷當前情況下是否要進行擴容。

table擴容

如果table中的元素數量達到閾值threshold的3/4,會進行擴容操作,過程很簡單:

  1. 新建新的陣列newTab,大小為原來的2倍。
  2. 複製table的元素到newTab,忽略陳舊的元素,假設table中的元素e需要複製到newTab的i位置,如果i位置存在元素,則找下一個空位置進行插入。

ThreadLocal.get() 實現

獲取當前的執行緒的threadLocals。

  1. 如果threadLocals不為null,則通過ThreadLocalMap.getEntry方法找到對應的entry,如果其引用和當前key一致,則直接返回,否則在table剩下的元素中繼續匹配。
  2. 如果threadLocals為null,則通過setInitialValue方法初始化,並返回。

總結

希望通過本文的介紹,大家可以對ThreadLocal有一個更加直觀清晰的認識,而不是隻見葉子,不見森林。

打賞支援我寫出更多好文章,謝謝!

打賞作者

打賞支援我寫出更多好文章,謝謝!

深入淺出 ThreadLocal