ThreadLocal類該類主要用於不同執行緒儲存自己的執行緒本地變數。本文先通過一個示例簡單介紹該類的使用方法,然後從ThreadLocal類的初始化、儲存結構、增刪資料和hash值計算
2021-06-02 23:12:53
ThreadLocal類
該類主要用於不同執行緒儲存自己的執行緒本地變數。本文先通過一個示例簡單介紹該類的使用方法,然後從ThreadLocal類的初始化、儲存結構、增刪資料和hash值計算等幾個方面,分析對應源碼。採用的版本為jdk1.8。
ThreadLocal物件可以在多個執行緒中被使用,通過set()方法設定執行緒本地變數,通過get()方法獲取設定的執行緒本地變數。我們先通過一個示例簡單瞭解下使用方法:
由於threadlocal設定的值是在每個執行緒中都有一個副本的,執行緒之間不會互相影響。程式碼運行的結果如下所示:
ThreadLocal-初始化
ThreadLocal類只有一個無參的構造方法,如下所示:
但其實還有一個帶參數的構造方法,不過是它的子類。ThreadLocal中定義了一個內部類SuppliedThreadLocal,為繼承自ThreadLocal類的子類。可以通過該類進行給定初始值的初始化,其定義如下:
通過TheadLocal threadLocal = Thread.withInitial(supplier);這樣的語句可以進行給定初始值的初始化。在某個執行緒第一次呼叫get()方法時,會執行initialValue()方法設定執行緒變數為傳入supplier中的值。
ThreadLocal-儲存結構
在jdk1.8版本中,使用的是TheadLocalMap這一個容器儲存執行緒本地變數。
該容器的設計思想和HashMap有很多共同之處。比如:內部定義了Entry節點儲存鍵值對(使用ThreadLocal物件作為鍵);使用一個數組儲存entry節點;設定一個閾值,超過閾值時進行擴容;通過鍵的hash值與陣列長度進行&操作確定下標索引等。但也有很多不同之處,具體我們在後續介紹ThreadLocalMap類時再詳細分析。
ThreadLocal-增刪資料
ThreadLocal類提供了get(),set()和remove()方法來操作當前執行緒的threadlocal變數副本。底層則是基於ThreadLocalMap容器來實現資料操作。
不過要注意的是:ThreadLocal中並沒有ThreadLocalMap的成員變數,ThreadLocalMap物件是Thread類中的一個成員,所以需要通過通過當前執行緒的Thread物件去獲取該容器。
每一個執行緒Thread物件都會有一個map容器,該容器會隨著執行緒的終結而回收。
設定執行緒本地變數的方法。
獲取執行緒本地變數的方法。
移除執行緒本地變數的方法
ThreadLocal-hash值計算
ThreadLocal的hash值用於ThreadLocalMap容器計算陣列下標。類中定義threadLocalHashCode表示其hash值。類中定義了靜態方法和靜態原子變數計算hash值,也就是說所有的threadLocal物件共用一個增長器。
我們使用同樣的方法定義一個測試類,定義多個不同測試類物件,看看hash值的生成情況。如下所示,可以看到hash值都不同,是共用的一個增長器。
ThreadLocalMap類
ThreadLocalMap類是ThreadLocal的內部類。其作為一個容器,為ThreadLocal提供操作執行緒本地變數的功能。每一個Thread物件中都會有一個ThreadLocalMap物件例項(成員變數threadLocals,初始值為null)。因為map是Thread物件的非公共成員,不會被併發呼叫,所以不用考慮併發風險。
後文將從資料儲存設計、初始化、增刪資料等方面分析對應源碼。
ThreadLocalMap-資料儲存設計
該map和hashmap類似,使用一個Entry陣列來儲存節點元素,定義size變量表示當前容器中元素的數量,定義threshold變數用於計算擴容的閾值。
不同的是Entry節點為WeakReference類的子類,使用引用欄位作為鍵,將弱引用欄位(通常是ThreadLocal物件)和值繫結在一起。使用弱引用是為了使得threadLocal物件可以被回收,(如果將key作為entry的一個成員變數,那執行緒銷燬前,threadLocal物件不會被回收掉,即使該threadLocal物件不再使用)。
ThreadLocalMap-初始化
提供了帶初始鍵和初始值的map構造方法,還有一個基於已有map的構造方法(用於ThreadLocal的子類InheritableThreadLocal初始化map容器,目的是將父執行緒的map傳入子執行緒,會在創建子執行緒的過程中自動執行)。如下所示:
ThreadLocalMap-移除元素
這裡將移除元素的方法放在前面,是因為其它部分會頻繁使用過時節點的移除方法。先理解這部分內容有助於後續理解其他部分。
根據key移除容器元素的方法:
移除過時節點的執行方法:
移除過時節點除了將該節點置為null之外,還要對該節點之後的節點進行移動,看看能不能往前找合適的空格轉移。
這種方法有點類似jvm垃圾回收演算法的標記-整理方法。都是將垃圾清除之後,將剩餘元素進行整理,變得更緊湊。這裡的整理是需要強制執行的,目的是為了保證開放地址法一定能在連續的非null節點塊中找到已有節點。(試想,如果把過時節點移除而不整理,該節點為null,將前後節點分開了。而如果後面有某個節點hash計算的下標在前面的節點塊,在查詢節點時通過開放地址會找不到該節點)。示意圖如下:
移除所有過時節點的方法:很簡單,全局遍歷,移除所有過時節點。
嘗試去掃描一些過時節點並清除節點,如果有節點被清除會返回true。這裡只執行了logn次掃描判斷,是為了在不掃描和全局掃描之間找到一種平衡,是上面的方法的一個平衡。
ThreadLocalMap-獲取元素
獲取容器元素的方法:
ThreadLocalMap-增加和修改元素
增加和修改容器元素的方法:
這裡在根據hash值計算出下標後,由於是開放地址解決hash衝突,會順序向後遍歷直到遇到null或遇到key對應的節點。
這裡會出現三種情況:
case1:遍歷時找到了key對應節點,這時直接修改節點的值即可;
case2:遍歷中遇到了有過時的節點(key被回收的節點);
case3:遍歷沒有遇到過時的節點,也沒有找到key對應節點,說明此時應該插入新節點(用輸入鍵值構造新節點)。因為是增加新元素,所以可以容量會超過閾值。在刪除節點後容量如果超過閾值,則要進行擴容操作。
case2:增加和修改過程中遇到已經過時的節點的處理。這裡的參數staleSlot表示key計算的下標開始往後遇到的第一個過時節點,不管map中有無key對應的節點,該位置之後一定會存入key的節點。這裡定義了一個變數slotToExpunge,其含義是左右連續非null的entry塊中第一個過時節點(記錄該位置是為了後續清除過時節點可以從slotToExpunge處開始)。示意如下:
這步操作有兩種情況:
casse2.1:從過時節點staleSlot往後查詢遇到key對應節點,則將staleSlot處節點與key對應節點交換。然後清除整理連續塊。
casse2.2:沒遇到key對應節點,說明map中不存在key對應節點,則新建一個節點填入staleSlot處。然後清除整理連續塊。
case3:增加元素後可能超過閾值導致的擴容處理
ThreadLocalMap-記憶體洩露問題以及對設計的一些思考
先來聊一聊記憶體洩漏這個概念。我的理解是有一塊記憶體空間,如果不再被使用但又不能被垃圾回收器回收掉,那麼就相當於這塊記憶體少了這塊空間,即出現了記憶體洩露問題。如果記憶體洩露的空間一直在積累,那麼最終會導致可用空間一直減少,最終可能導致程式無法運行。
ThreadLocalMap中也是有可能會出現該問題的,map中entry節點的key為弱引用,如果key沒有其它強引用,是會被垃圾收集器回收的。回收之後,map中該節點的value就不會再被使用,但value又被entry節點強引用,不會被回收。這就相當於value這塊記憶體空間發生了洩露。所以能看到在源碼中很多方法都進行了清除過時節點的操作,為的就是儘量避免記憶體洩漏。
在看源碼時,一直在思考為什麼entry節點的鍵要採用弱引用的方式。不妨反過來思考,如果entry節點將threadLocal物件作為一個成員變數,而不是採用弱引用的方式,那麼entry節點一直對key和value保持著強引用關係,即使threadlocal物件在其它地方都不再使用,該物件也不會被回收。這就會導致entry節點永遠不會被回收(只要執行緒不終結),而且也不能主動去判斷是否切斷map中threadlocal物件的引用(不知道是否還有其它地方引用到了)。
因為map是Thread物件的一個成員變數,執行緒不終結,map是不會被回收的,如果發生了記憶體洩露的問題,可能會一直積累下去,最終導致程式發生異常。而key採用弱引用加之主動的判斷過時節點(判斷是否過時很簡單,看key是否為null即可)並進行清除處理可以最大限度的減少記憶體洩露的發生。
各種Java資料,關注回覆「資料」獲取。
相關文章
ThreadLocal類該類主要用於不同執行緒儲存自己的執行緒本地變數。本文先通過一個示例簡單介紹該類的使用方法,然後從ThreadLocal類的初始化、儲存結構、增刪資料和hash值計算
2021-06-02 23:12:53
5月31日,康得新正式被深交所摘牌,至此,這場鬧得沸沸揚揚的財務資料造假案塵埃落定。不過,正當萬千股民哀嚎之時,康得新卻再次傳出被起訴的訊息。雙重打擊根據證監會的公告,康得新
2021-06-02 23:10:27
在今日舉行的HarmonyOS 2及華為全場景新品釋出會上,除了備受矚目的HarmonyOS以及精彩紛呈的諸多華為新品,華為也為Mate40系列等機型推出了全新的使用者服務政策。此次服務煥新
2021-06-02 23:10:05
北京時間2021年6月1日下午2點,全球圖形技術領軍者NVIDIA在臺北電腦展Computex 2021正式釋出GeForce RTX 30系列顯示卡新成員——「NVIDIA GeForce RTX 3080Ti」,其仍舊採
2021-06-02 22:53:28
在華為鴻蒙作業系統的釋出會上,餘承東正式帶來了華為鴻蒙系統,相信大家都已經看過了,但是釋出會結束的比較倉促沒有說清升級的方式,小編給大家總結好了。 鴻蒙作業系統升級方式
2021-06-02 22:52:56
6月2日晚,「敢想敢搏——聯想消費生態夏日星光夜」活動(以下簡稱「夏日星光夜」)在北京聯想全球總部舉行。此次活動中,聯想品牌代言人王一博蒞臨現場,併發布三款定製版產品;聯想集
2021-06-02 22:52:01