如何去理解ThreadLocal?

雍保國


ThreadLocal是什麼?

ThreadLocal是一個本地線程副本變量工具類。主要用於將私有線程和該線程存放的副本對象做一個映射,各個線程之間的變量互不干擾,在高併發場景下,可以實現無狀態的調用,特別適用於各個線程依賴不通的變量值完成操作的場景.

ThreadLocal特點:就是在一個線程裡放一個數據,不管中間執行了什麼操作。只要想獲取出來的時候,調用get就可以得到保存進去的數據.


ThreadLocal內部結構圖



從上面的結構圖中,我們可以看到ThreadLocal的核心機制

  • 每個Thread 內部都有一個Map。
  • Map裡面存儲線程本地對象(key) 和線程的變量副本(value)。
  • Thread 內部的Map是由 ThreadLocal為的,由ThreadLocal負責向map獲取和設置線程的變量值。

Thread線程內部的Map在類中描述如下:

ThreadLocal 為什麼會內存洩漏

我們先分析一下ThreadLocalMap

我們可以知道每個Thread 維護一個 ThreadLocalMap,這個映射表的 key 是 ThreadLocal實例本身,value 是真正需要存儲的 Object,也就是說 ThreadLocal 本身並不存儲值,它只是作為一個 key 來讓線程從 ThreadLocalMap 獲取 value。仔細觀察ThreadLocalMap,這個map是使用 ThreadLocal 的弱引用作為 Key 的,弱引用的對象在 GC 時會被回收。

這樣,當把threadlocal變量置為null以後,沒有任何強引用指向threadlocal實例,所以threadlocal將會被gc回收。這樣一來,ThreadLocalMap中就會出現key為null的Entry,就沒有辦法訪問這些key為null的Entry的value,如果當前線程再遲遲不結束的話,這些key為null的Entry的value就會一直存在一條強引用鏈:Thread Ref -> Thread -> ThreaLocalMap -> Entry -> value,而這塊value永遠不會被訪問到了,所以存在著內存洩露。

其實java 開發者,也考慮到了此問題,所以在get(),set()的時候,調用了expungeStaleEntry方法用來清除Entry中Key為null的Value,但是這是不及時的,也不是每次都會執行的,所以一些情況下還是會發生內存洩露。只有remove()方法中顯式調用了expungeStaleEntry方法。

下面看下ThreadLocal 的get()方法的實現:

下面繼續看 map.getEntry方法

當key 為null 時,調用getEntryAfterMiss方法

當key 為null 時,調用expungeStaleEntry 方法

也許有人會好奇,有上面方法,為什麼還會導致內存洩漏呢?

  • 一般我們設置的ThreadLocal設置為static的,static 變量可以作為GCRoot的根節點,所以會一直存在
  • 初始化了ThreadLocal, 調用set ,get 而沒有調用remove方法,所以會導致內存洩漏。

比如get方法,只有ThreadLocalMap中沒有所需要的key時,才會調用清除方法


碼農的一天


1. ThreadLocal的值是存在它自己的內部類ThreadLocalMap的對象中的,ThreadLocalMap內部又定義了一個內部類Entity用來封裝ThreadLocalMap的k-v

2. Thread的一個成員變量是ThreadLocalMap類型

3 也就是說,ThreadLocal的存取值是依賴於當前線程的,值是存在當前線程的屬性中,無論ThreadLocal定義在哪,set和get都是要調用當前線程對象並在其中存取,都是開闢的單獨的空間,

4 不同線程下,操作的都是同一對象的副本,對象的屬性功能都在,卻相互獨立。ThreadLocal的對象自動跟當前線程對象建立聯繫。

5 比如DB連接中的con,就創建了一個,但多個線程都可以用,就是因為他們操作的是con的副本。

6 此時再回頭看 ThreadLocal的命名,就容易理解了,線程的局部變量,從本質上說,通過ThreadLocalMap做中介,ThreadLocal對象通過set方法給當前Thread的一個成員變量賦值。ThreadLocal就相當於Thread的一個工具類,有2個作用:

(1) 定義ThreadLocalMap供Thread使用

(2) 為Thread的ThreadLocalMap屬性threadLocals提供維護接口。


木法沙和三傻


ThradLocal就是通過當前線程id作為建映射到一個共享變量副本到當前線程堆棧,這樣各個線程都有一個共享變量副本,就可以避免多個線程之間搶佔共享變量資源導致發生髒讀,幻度的情況,做到線程隔離。


分享到:


相關文章: