1.在沒有ThreadLocal遇到的問題:
在多線程編程領域,多個線程同時訪問同一個變量時,數據一致性成為關鍵挑戰。為防止修改數據時出現覆蓋問題,傳統解決方案是采用加鎖機制,讓線程排隊依次訪問共享變量。然而,這種 “排隊” 策略不可避免地消耗時間,在高并發場景下,性能損耗尤為明顯,成為程序效率提升的瓶頸。
加鎖機制的局限性
加鎖的本質是強制線程排隊,確保同一時刻僅有一個線程操作共享變量。但這一過程伴隨著線程的等待與調度,增加了額外的時間開銷。尤其在競爭激烈的場景中,頻繁的加鎖、解鎖操作會嚴重影響程序的執行效率,無法滿足高性能需求。
“空間換時間” 的巧妙突破
從 “空間換時間” 的視角出發,可嘗試為每個線程復制一份變量副本。如此一來,每個線程僅操作自己的專屬副本,彼此互不干擾,既保障了數據安全,又徹底消除了等待時間。這一思路在生活中有諸多類似場景:
- 火車站商務候車廳:為特定乘客(線程)提供獨立空間(副本),減少公共區域(共享資源)的擁擠與等待。
- 主臥獨立廁所:家庭成員(線程)各自使用專屬空間(副本),避免共用廁所(共享資源)時的排隊。
在編程領域,這種思路典型地體現在?ThreadLocal
?等機制中。它為每個線程提供專屬的變量存儲,線程僅需操作自己的副本,無需與其他線程競爭,在數據安全與執行效率間找到了完美平衡。
2.對副本變量特征進行分析:
圖中對副本變量特征進行分析:
- 變量呈現 “key→value” 格式,而?
Map
?結構正是以鍵值對形式存儲數據,因此適用?Map
?結構存儲。 - 涉及存值、取值、刪除值操作,這些是?
Map
?結構的常見操作,能夠很好地支持。 - 存儲數量不多,使用?
Map
?結構不會造成過大開銷,較為合適。
綜上,選擇?Map
?結構存儲副本變量,是因它匹配 “key→value” 格式,支持相關操作,且在數據量不大時能有效工作。
3.Map的底層數據結構
圖中內容主要解析?Map
?底層數據結構的設計思路:
- 計算機底層數據結構包含數組與鏈表,數組可通過下標直接獲取值。
Map
?以?key→value
?形式組織數據,若能讓?key
?映射到一個數字(如通過哈希函數計算下標),就可借助數組存儲,實現快速訪問?value
。這是許多?Map
?實現(如?HashMap
)的底層邏輯基礎,通過哈希將?key
?映射到數組位置,結合鏈表或紅黑樹處理沖突,在存儲和查詢效率上達到優化。
4.Map的實現
圖中介紹了?Map
?實現中的兩個關鍵問題:哈希沖突與數組擴容,針對哈希沖突,主要有鏈表法和開放尋址法兩種解決方案,具體解析如下:
- 鏈表法:
- 實現:為散列表每個位置創建鏈表存儲元素,采用 “數組 + 鏈表” 形式。
- 優點:處理沖突簡單,無堆積現象,平均查找長度短;鏈表結點動態申請,適合構造表時長度不確定的情況;刪除結點操作易于實現,只需刪除鏈表上相應結點。
- 缺點:指針需要額外空間,當結點規模較小時,開放定址法更節省空間。
- 現實場景類比:在操場開元旦晚會,每個班級有固定位置,后來的班級排在后面(若鏈表過長,如后面太遠看不見,可能通過紅黑樹優化)。
- 開放尋址法:
- 實現:一旦發生沖突,就尋找下一個空的散列地址存儲記錄,只要散列表足夠大,總能找到空地址。
- 優點:當結點規模較小時,相對節省空間。
- 缺點:容易產生堆積問題,不適用于大規模數據存儲;散列函數的設計對沖突影響大,插入時可能多次沖突;若刪除的元素是多個沖突元素中的一個,需對后面元素作處理,實現較復雜。
- 現實場景類比:網吧包間,你去之前跟朋友說定一個位置(若指定包間被占,順著找下一個空的),朋友按此方法找你。
綜上,兩種方法各有優劣,實際應用中需根據場景(如數據規模、操作特點等)選擇合適的沖突解決策略,以優化?Map
?的性能。
ThreadLocal的官方實現
工具類特性:ThreadLocal
?類的一個實例綁定一個變量,提供存值、取值、刪除值三個操作方法,方便對線程本地變量進行管理。
底層實現:ThreadLocal
?內部實現?Map
?的底層數據存取,采用開放尋址法解決?Map
?中的哈希沖突問題,并進行了優化,確保數據存儲與獲取的高效性。
數據存儲位置:將副本變量的數據存放在線程自身中,每次數據操作直接針對線程自身的屬性,實現線程間數據隔離。
總結:ThreadLocal
?本身不存儲值,而是訪問當前線程?ThreadLocalMap
?里存儲的數據副本,有效實現了線程間的數據隔離,避免多線程環境下的數據競爭問題。
? ? ? ? 這句話揭示了 ThreadLocal 的核心機制: ThreadLocal 本身并非實際存儲數據的容器,而是作為一個 “橋梁” 或 “訪問入口” 存在。每個線程內部都有一個專屬的 ThreadLocalMap(類似于一個小型的鍵值對存儲結構),當通過 ThreadLocal 調用 set() 方法存儲值時,實際上是將數據以 ThreadLocal 自身作為鍵,存入當前線程的 ThreadLocalMap 中;調用 get() 方法時,也是從當前線程的 ThreadLocalMap 中獲取與該 ThreadLocal 關聯的值。
? ? ? ?舉個簡單例子,就像每個線程有一個 “私人儲物柜”(ThreadLocalMap),ThreadLocal 就像這個柜子的 “鑰匙”,通過這把 “鑰匙” 操作的始終是當前線程自己 “柜子” 里的數據,與其他線程的 “柜子” 無關,從而實現了線程間的數據隔離。這樣,每個線程都在自己的 ThreadLocalMap 中維護變量的副本,ThreadLocal 并不直接存儲值,只是提供了對當前線程內 ThreadLocalMap 中數據副本的訪問方式。
Threadlocal與Thread的關系
ThreadLocal
?類代碼:
public class ThreadLocal<T> {// 構造函數public ThreadLocal() {}// 獲取當前線程綁定的變量值public T get() {Thread t = Thread.currentThread(); // 獲取當前線程ThreadLocalMap map = getMap(t); // 獲取當前線程的ThreadLocalMap// 省略后續從map中獲取值的代碼return null;}// 設置當前線程綁定的變量值public void set(T value) {Thread t = Thread.currentThread(); // 獲取當前線程ThreadLocalMap map = getMap(t); // 獲取當前線程的ThreadLocalMap// 省略后續在map中設置值的代碼if (map != null)map.set(this, value);elsecreateMap(t, value);}// 獲取線程的ThreadLocalMapThreadLocalMap getMap(Thread t) {return t.threadLocals; // 每個線程Thread都有threadLocals屬性,類型是ThreadLocalMap}// 創建新的ThreadLocalMap并綁定到線程void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue); // 將新的ThreadLocalMap設置到線程的threadLocals屬性}// ThreadLocal的內部類ThreadLocalMap,實現數據存儲static class ThreadLocalMap {// 內部Entry類,繼承WeakReference,鍵為ThreadLocalstatic class Entry extends WeakReference<ThreadLocal<?>> {Object value; // 存儲的值Entry(ThreadLocal<?> k, Object v) {super(k); // 調用父類WeakReference的構造函數,傳入ThreadLocal作為引用value = v; // 設置值}}private Entry[] table; // 存儲Entry的數組// ThreadLocalMap的構造函數ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY]; // 初始化數組int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); // 計算哈希位置table[i] = new Entry(firstKey, firstValue); // 在計算出的位置創建Entry}}
}
Thread
?類代碼:
public class Thread implements Runnable {// 省略其他代碼ThreadLocal.ThreadLocalMap threadLocals = null; // 每個線程都有自己的 ThreadLocalMap 實例,用于存儲線程本地變量ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; // 用于支持線程變量繼承的 ThreadLocalMap// 省略其他代碼
}
類圖關系
Thread 提供存儲場所(ThreadLocalMap)
每個 Thread 類內部都有一個類型為 ThreadLocal.ThreadLocalMap 的成員變量 threadLocals。
ThreadLocalMap 是 ThreadLocal 的靜態內部類,本質是一個特殊的 “容器”,用于存儲線程局部變量。
當線程通過 ThreadLocal 操作變量時,實際是在操作該線程自身的 threadLocals(即 ThreadLocalMap)。例如,調用 threadLocal.set(value) 時,會以 threadLocal 自身為鍵,將 value 存入當前線程的 threadLocals 中,實現數據的線程內隔離存儲。
每個線程(Thread
)內部維護一個 ThreadLocalMap
,這是 ThreadLocal
存儲數據的核心結構。
ThreadLocalMap
?的作用:- 每個線程的?
ThreadLocalMap
?是一個哈希表,用于存儲該線程的線程局部變量。 - 鍵(Key)是?
ThreadLocal
?對象,值(Value)是線程的變量副本。 - 通過?
ThreadLocalMap
,每個線程可以獨立地存儲和訪問自己的數據,而不會與其他線程沖突。
- 每個線程的?
ThreadLocal 提供操作接口(set、get、remove 等方法)
ThreadLocal
?類提供了一系列簡潔的方法,封裝了對?ThreadLocalMap
?的操作細節:
-
set(T value)
?方法:
public void set(T value) { Thread t = Thread.currentThread(); // 獲取當前線程 ThreadLocalMap map = getMap(t); // 獲取當前線程的 ThreadLocalMap if (map != null) map.set(this, value); // 以當前 ThreadLocal 為鍵,存入值 else createMap(t, value); // 若 ThreadLocalMap 不存在,創建并存儲
}
-
該方法先獲取當前線程及其?
ThreadLocalMap
,若?ThreadLocalMap
?已存在,直接以當前?ThreadLocal
?為鍵存儲值;若不存在,則創建新的?ThreadLocalMap
?并存儲。
- 流程:
- 獲取當前線程的?
ThreadLocalMap
。 - 如果?
map
?存在,直接將當前?ThreadLocal
?對象作為鍵,傳入的?value
?作為值存入?map
。 - 如果?
map
?不存在(線程首次調用?set
),則創建新的?ThreadLocalMap
?并初始化數據。
- 獲取當前線程的?
- 關鍵點:
- 每個線程的?
ThreadLocalMap
?是獨立的,因此不同線程的?set
?操作互不影響。 ThreadLocal
?對象本身作為鍵,確保每個線程只能訪問自己的數據副本。
- 每個線程的?
-
get()
?方法:
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); // 若未獲取到值,設置初始值并返回
}
-
該方法從當前線程的?
ThreadLocalMap
?中查找以當前?ThreadLocal
?為鍵的值并返回,若未找到則設置初始值。
- 流程:
- 獲取當前線程的?
ThreadLocalMap
。 - 如果?
map
?存在,嘗試根據當前?ThreadLocal
?對象作為鍵查找對應的值。 - 如果找到,返回值;否則調用?
setInitialValue()
?初始化默認值。
- 獲取當前線程的?
- 關鍵點:
get
?方法始終操作當前線程的?ThreadLocalMap
,確保線程隔離。- 如果未顯式調用?
set
,首次?get
?會觸發?initialValue()
?初始化(默認返回?null
)。
-
remove()
?方法:
public void remove() { ThreadLocalMap m = getMap(Thread.currentThread()); if (m != null) m.remove(this); // 從當前線程的 ThreadLocalMap 中刪除當前 ThreadLocal 對應的鍵值對
}
-
該方法從當前線程的?
ThreadLocalMap
?中刪除以當前?ThreadLocal
?為鍵的鍵值對,避免內存泄漏。
- 流程:
- 獲取當前線程的?
ThreadLocalMap
。 - 如果?
map
?存在,移除當前?ThreadLocal
?對象對應的鍵值對。
- 獲取當前線程的?
- 關鍵點:
- 必須手動調用?
remove()
:避免內存泄漏。 - 如果線程池中的線程長期存活,不清除?
ThreadLocalMap
?中的值會導致殘留數據污染后續任務。
- 必須手動調用?
通過這些方法,開發者無需關心?ThreadLocalMap
?的底層實現(如哈希沖突處理、數組擴容等),直接通過?ThreadLocal
?即可便捷地管理線程局部變量,實現數據的存儲、獲取和刪除,同時保證線程間數據的獨立性。
綜上,Thread 通過?threadLocals
?成員變量提供存儲結構,ThreadLocal 通過?set
、get
、remove
?等方法封裝操作邏輯,二者協作實現了高效、安全的線程局部變量管理。
ThreadLocalMap 的內部結構
ThreadLocalMap
是 ThreadLocal
的靜態內部類,它是一個定制化的哈希表,專門用于存儲線程局部變量。以下是其核心設計:
Entry
?結構
static class Entry extends WeakReference<ThreadLocal<?>> {Object value; // 實際存儲的值Entry(ThreadLocal<?> k, Object v) {super(k); // Key 是弱引用(防止內存泄漏)value = v;}
}
- Key 是弱引用:
ThreadLocal
?對象作為鍵時,使用?WeakReference
?包裝。- 當?
ThreadLocal
?對象不再被強引用時,GC 會回收它,避免內存泄漏。
- Value 是強引用:
- 值對象不會被自動回收,必須顯式調用?
remove()
?清理。
- 值對象不會被自動回收,必須顯式調用?
Thread 類和 ThreadLocal 類在 Java 中是兩個獨立的類,它們沒有繼承關系。Thread 類是 Java 中用于創建和管理線程的類,而 ThreadLocal 類是用于為每個使用它的線程都單獨存儲一份獨立的變量副本。
協作關系
Thread 和 ThreadLocal 是協作關系,Thread 為 ThreadLocal 提供存儲數據的場所(ThreadLocalMap),ThreadLocal 為 Thread 提供了方便的操作接口(如 set、get、remove 方法)來管理線程局部變量。這種協作實現了線程間數據的隔離,每個線程可以獨立地操作自己的局部變量,互不干擾。