面試官:ThreadLocal 瞭解嗎?
Python 小星:線程局部變量,多線程下能保證各個線程的變量相對獨立於其他線程的變量。
面試官:那你說下它是如何保證線程隔離的?
Python 小星:每個線程維護一個 ThreadLocalMap ,ThreadLocalMap 和 HashMap 結構類似,key 是 ThreadLocal,通過 hash 算法取餘平均分配到數組上,value 是我們需要的局部變量。有與 key 的唯一性,保證了線程隔離。
面試官:那你說說 ThreadLocalMap 是數組加鏈表的結構解決衝突的嗎?
Python 小星:我的印象裡不是鏈表結構。
面試官:那他是如何解決衝突的?
Python 小星:......
面試官:創建多線程異步執行業務邏輯時,該 ThreadLocal 變量並不能傳遞到子線程中,如何讓父子線程共享變量?
Python 小星:不太清楚
面試官:InheritableThreadLocal 可以下去了解下。今天就先到這,回去等通知吧
什麼是 ThreadLocal?(以下簡稱 TL)
看下JDK源碼註釋
翻譯過來:
TL 用來提供線程內部局部變量。這種變量在多線程環境下訪問(通過 get 和 set 方法訪問)時能保證各個線程的變量相對獨立於其他線程的變量。TL 實例通常來說都是 private static 類型,用於關聯線程和線程上下文。
簡單說,ThreadLocal 為每個使用該變量的線程提供獨立的變量副本。所以每個線程都可以獨立地改變自己的副本,而不會影響其他線程所對應的副本,我們通常把這叫“線程隔離”。
ThreadLocal 的基本使用
1、常用方法
2、如果不是 TL,會出現什麼問題???
輸出結果(多運行幾次):
我們可以發現,線程 0 輸出了線程 2 的數據。
3、使用 TL
TL 如何實現線程隔離???
1、JDK 早期設計
熟悉 hashmap 的老鐵們,比較容易理解:每個 TL 創建一個 Map,然後用線程作為 Map 的 key,要存儲的局部變量作為 Map 的 value,這樣達到線程隔離的目的。
這麼做有一個弊端:
當線程回收時,該線程綁定的變量不能被自動的回收,因為變量存儲在 ThreadLocal 裡,必須顯式的去回收。
2、JDK 1.8 的設計
每個 Thread 維護一個 ThreadLocalMap,這 map 的 key 是 ThreadLocal 實例本身,value 才是真正要存儲的變量值。
① 每個 Thread 內部都有一個 ThreadLocalMap
② ThreadLocalMap 裡面存儲 TL 對象(key) 和 線程的變量副本(value)
③ Thread 內部的 ThreadLocalMap 是由 TL 維護的,由 TL 負責向 ThreadLocalMap 設置和獲取變量值
④ 對於不同的線程,每次獲取副本值時,別的線程並不能獲取當前線程的副本值,形成副本的隔離,互不干擾
好處:
① 每個 ThreadLocalMap 存儲的 Entry 數量變少
② 當 Thread 銷燬時,ThreadLocalMap 也隨之銷燬,減少內存的使用
TL 源碼
1、set 方法
① 首先獲取當前線程,並根據當前線程獲取一個 map
② 如果獲取的 map 不為空,則將參數設置到 map 中(當前的 TL 的引用設置為 key)
③ 如果 map 為空,則給該線程創建 map ,並設置初始值
2、get 方法
① 獲取當前線程,然後根據當前線程獲取 map
② 如果 map 不為空,則以 TL 的引用為 key 獲取 map 中對應的 Entry e,否則到第 ④ 步
③ 如果 e 不為空,獲取對應的 value 值,否則到第 ④ 步
④ map 為空 或者 e 為空,通過 initialValue ,也就是 NULL,然後用 TL 的引用和 value 作為 firstKey 和 firstValue 創建新的 map
總結:獲取當前線程的 ThreadLocalMap,如果存在則返回值,不存在則創建並返回值。
3、remove
4、ThreadLocalMap
ThreadLocalMap 是 TL 的內部類,沒有實現 map 接口。
從圖中,我們可以發現 SHI 和 HashMap 類似。
Entry 繼承 WeakReference,也就是說 key 是弱引用。
弱引用和內存洩漏
1、弱引用相關概念
① java 引用
4 種類型:強、弱、軟、虛
② 強引用和弱引用
【強引用】:最常見的 new 一個對象,只要強引用指向一個對象,GC 就不會回收
【弱引用】:GC 一旦發現弱引用的對象,不管當前內存空間足夠與否,都會回收它的內存
2、內存溢出和內存洩漏
① 內存溢出(memory overflow)
沒有足夠的內存提供申請者使用。就像一個開水瓶,當你倒水時,如果大於水瓶容量,水就會溢出來。
② 內存洩漏(memory leak)
動態分配的堆內存由於某種原因未釋放或者無法釋放,造成系統內存的浪費,內存洩漏的堆積將造成內存洩漏。
為什麼 TL 裡使用弱引用?
首先我們明確 key 是使用了弱引用,當把 threadlocal 實例置為 null 以後,沒有任何強引用指向 threadlocal 實例,所以 threadlocal 就可以順利被 gc 。
我們從圖中也能看到雖然 key 被 gc 回收了,但是 value 還在呀。因為存在一條從 current thread 連接過來的強引用。只有當前 thread 結束以後,current thread 就不會存在棧中,強引用斷開,Current Thread, Map, value 將全部被 GC 回收。
有人會問:無論是強引用還是弱引用,都會出現內存洩漏?用弱引用的好處到底體現在哪?
弱引用會將 key 設置為 null,當使用 map 的 get 和 set 方法時,會判斷 key 是否為 null ,如果為 null,則將 value 設置為 null。弱引用比強引用在 thread 結束之前多一層屏障。
如何解決內存洩漏問題
當線程的某個 localThread 使用完,然後調用 threadlocal 的 remove 方法。
ThreadLocalMap 和 HashMap 的區別?
ThreadLocalMap 和 HashMap 最大的區別在於解決 hash 衝突。
HashMap 使用的拉鍊法,而 ThreadLocalMap 使用的線性探測法。
簡單說:
當我們通過 int i = key.threadLocalHashCode & (len-1) 計算出 hash 值,如果出現衝突,順序查看錶中下一單元,直到找出一個空單元或查遍全表。
正因為採取的線性探測法解決衝突,所以在查找的時候,必須比較 key 值是否相等,否則順序尋找下一個單元。
父子線程如何共享變量?
使用 InheritableThreadLocal 來解決,底層原理的話各位看官下去看看,下次再續。
輸出結果:
| 文
閱讀更多 Python大星 的文章