ThreadLocal 總結

什麼是ThreadLocal

ThreadLocal,顧名思義,它不是一個線程,而是線程的一個本地化對象。

當工作於多線程中的對象使用 ThreadLocal 維護變量時,ThreadLocal 為每個使用該變量的線程分配一個獨立的變量副本。

所以每一個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本。從線程的角度看,這個變量就像是線程的本地變量,這也是類名中 “Local” 所要表達的意思。

ThreadLocal 提供了線程的局部變量副本,每個線程都可以通過set()和get()來對這個局部變量進行操作,但不會和其他線程的局部變量進行衝突,實現了線程的數據隔離~。

其實就是你創建了一個 Threadlocal 變量,每個訪問 Threadlocal 變量的線程都有一個本地副本。

往ThreadLocal 中填充的變量屬於當前線程,該變量對其他線程而言是隔離的。


ThreadLocal數據結構

ThreadLocal 總結

一個 ThreadLocal 只能存儲一個 Object 對象,如果需要存儲多個 Object 對象那麼就需要多個 ThreadLocal

ThreadLocal 總結

  • ThreadLocalMap 的 Entry 對 ThreadLocal 的引用為弱引用,避免了 ThreadLocal 對象無法被回收的問題
  • ThreadLocalMap 的 set 方法通過調用 replaceStaleEntry 方法回收鍵為 null 的 Entry 對象的值(即為具體實例)以及 Entry 對象本身從而防止內存洩漏

複習一下java的對象引用

• 強引用:new 出來的一般對象,只要引用在就不會被回收

• 軟引用: 將要發生內存溢出之前回收

• 弱引用: 生存到下一次垃圾收集發生之前

• 虛引用:目的是對象被收集器回收時收到一個系統通知

threadLocal 本身並不存儲值,它只是作為一個 key 來讓線程從 ThreadLocalMap 獲取 value。

ThreadLocalMap 是使用 ThreadLocal 的弱引用作為 Key 的,弱引用的對象在 GC 時會被回收。


Threadlocal 的 key 是弱引用,那麼在 threadlocal.get() 的時候,發生 GC 之後,key 是否是 null ?

做 threadlocal.get() 操作,證明其實還是有強引用存在的。所以 key 並不為 null 。

ThreadLocal 為什麼會造成內存洩漏?

ThreadLocalMap 使用 ThreadLocal 的弱引用作為key,如果一個 ThreadLocal 沒有外部強引用來引用它,那麼系統 GC 的時候,這個 ThreadLocal 勢必會被回收,這樣一來,ThreadLocalMap 中就會出現key為 null 的 Entry,就沒有辦法訪問這些key 為null 的 Entry 的 value ,如果當前線程再遲遲不結束的話,這些 key 為 null 的 Entry 的 value 就會一直存在一條強引用鏈:

Thread Ref -> Thread -> ThreaLocalMap -> Entry ->value

永遠無法回收,造成內存洩漏。

簡單來說,就是因為 ThreadLocalMap 的 key 是弱引用,當 TheadLocal 外部沒有強引用時,就被回收,此時會出現 ThreadLocalMap<null> 的情況,而線程沒有結束的情況下,導致這個 null 對應的 value 一直無法回收,導致洩漏。/<null>

ThreadLocal 內存洩漏的根源是:由於 ThreadLocalMap 的生命週期跟 Thread一樣長,如果沒有手動刪除對應 key 就會導致內存洩漏,而不是因為弱引用。

ThreadLocal 類型變量為何聲明為 static ?

ThreadLocal 類的目的是為每個線程單獨維護一個變量的值,避免線程間對同一變量的競爭訪問,適用於一個變量在每個線程中需要有自己獨立的值的場合。

如果把 threadLocalID 聲明為非靜態,則在含有 ThreadLocal 變量的的每個實例中都會產生一個新對象,這是毫無意義的,只是增加了內存消耗。


ThreadLocal的最佳實踐

  • ThreadLocal 並不解決多線程 共享 變量的問題
  • 如果要同時滿足變量在線程間的隔離與方法間的共享,ThreadLocal 再合適不過
  • 保存線程上下文信息,在任意需要的地方可以獲取
  • 線程安全的,避免某些情況需要考慮線程安全必須同步帶來的性能損失
  • 應該在我們不使用的時候,主動調用 remove 方法進行清理。
<code>try {
// 其它業務邏輯

} finally {
threadLocal對象.remove();
}/<code>


InheritableThreadLocal

ThreadLocal 固然很好,但是子線程並不能取到父線程的 ThreadLocal 的變量:

<code>private static ThreadLocal<integer> integerThreadLocal = new ThreadLocal<>();

public static void main(String[] args) throws InterruptedException {
integerThreadLocal.set(1001); // father

new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
+ integerThreadLocal.get())).start();
}
//output:
Thread-0:null/<integer>/<code>

使用 ThreadLocal 不能繼承父線程的 ThreadLocal 的內容,而使用 InheritableThreadLocal 時可以做到的,這就可以很好的在父子線程之間傳遞數據了。inheritableThreadLocal 繼承了 ThreadLocal。

<code>private static InheritableThreadLocal<integer> inheritableThreadLocal =
new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException {

inheritableThreadLocal.set(1002); // father
new Thread(() -> System.out.println(Thread.currentThread().getName() + ":"
+ inheritableThreadLocal.get())).start();
}
//output:
Thread-0:1002
/<integer>/<code>


其他 ThreadLocal 實現

Netty 的 FastThreadLocal

對 JDK 中 ThreadLocal 進行優化,由於 ThreadLocal 底層存儲數據是一個 ThreadLocalMap 結構,是一個數組結構,通過 threadLocalHashCode 查找在數組中的元素 Entry , 當 hash 衝突時,繼續向前檢測查找, 所以當 Hash 衝突時,檢索的效率就會降低。而 FastThreadLocal 則正是處理了這個問題,使其時間複雜度一直為O(1)。

TransmittableThreadLocal:

TransmittableThreadLocal 是Alibaba開源的、用於解決 “在使用線程池等會緩存線程的組件情況下傳遞 ThreadLocal ” 問題的 InheritableThreadLocal 擴展。

JDK 的 InheritableThreadLocal 類可以完成父線程到子線程的值傳遞。

但對於使用線程池等會池化複用線程的組件的情況,線程由線程池創建好,並且線程是池化起來反覆使用的;這時父子線程關係的ThreadLocal 值傳遞已經沒有意義,應用需要的實際上是把 任務提交給線程池時的 ThreadLocal 值傳遞到 任務執行時。

原理是使用 TtlRunnable/Ttlcallable包裝了 Runnable/Callable 類。


注意

spring 框架內部很多地方使用 ThreadLocal 來輔助實現,如事務管理。

但是Spring 根本就沒有對 bean 的多線程安全問題做出任何保證與措施。

對於每個bean 的線程安全問題,根本原因是每個 bean 自身的設計。

不要在 bean 中聲明任何有狀態的實例變量或類變量,如果必須如此,那麼就使用 ThreadLocal把變量變為線程私有的,如果 bean 的實例變量或類變量需要在多個線程之間共享,那麼就只能使用 synchronized、lock、CAS 等這些實現線程同步的方法了。


分享到:


相關文章: