“深入剖析ThreadLocal原理:從多線程數據隔離到內存泄漏防范“

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?并存儲。

  • 流程
    1. 獲取當前線程的?ThreadLocalMap
    2. 如果?map?存在,直接將當前?ThreadLocal?對象作為鍵,傳入的?value?作為值存入?map
    3. 如果?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?為鍵的值并返回,若未找到則設置初始值。

  • 流程
    1. 獲取當前線程的?ThreadLocalMap
    2. 如果?map?存在,嘗試根據當前?ThreadLocal?對象作為鍵查找對應的值。
    3. 如果找到,返回值;否則調用?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?為鍵的鍵值對,避免內存泄漏。

  • 流程
    1. 獲取當前線程的?ThreadLocalMap
    2. 如果?map?存在,移除當前?ThreadLocal?對象對應的鍵值對。
  • 關鍵點
    • 必須手動調用?remove():避免內存泄漏。
    • 如果線程池中的線程長期存活,不清除?ThreadLocalMap?中的值會導致殘留數據污染后續任務。

通過這些方法,開發者無需關心?ThreadLocalMap?的底層實現(如哈希沖突處理、數組擴容等),直接通過?ThreadLocal?即可便捷地管理線程局部變量,實現數據的存儲、獲取和刪除,同時保證線程間數據的獨立性。

綜上,Thread 通過?threadLocals?成員變量提供存儲結構,ThreadLocal 通過?setgetremove?等方法封裝操作邏輯,二者協作實現了高效、安全的線程局部變量管理。

ThreadLocalMap 的內部結構

ThreadLocalMapThreadLocal 的靜態內部類,它是一個定制化的哈希表,專門用于存儲線程局部變量。以下是其核心設計:

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 方法)來管理線程局部變量。這種協作實現了線程間數據的隔離,每個線程可以獨立地操作自己的局部變量,互不干擾。


源碼解析

ThreadLocalMap提供的方法



ThreadLocalMap提供的清理方法




面試題簡答







內存泄漏與強弱引用問題




強、弱引用面試題簡答












本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/903834.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/903834.shtml
英文地址,請注明出處:http://en.pswp.cn/news/903834.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

讀懂 Vue3 路由:從入門到實戰

在構建現代化單頁應用&#xff08;SPA&#xff09;時&#xff0c;Vue3 憑借其簡潔高效的特性成為眾多開發者的首選。 而 Vue3 路由&#xff08;Vue Router&#xff09;則是 Vue3 生態中不可或缺的一部分&#xff0c;它就像是單頁應用的 “導航地圖”&#xff0c;幫助用戶在不同…

Mac M1安裝 Docker Desktop 后啟動沒反應

Mac M1安裝 Docker Desktop 后啟動沒反應 如果在 Mac M1 上安裝 Docker&#xff0c;那最好的選擇是安裝 Docker Desktop但是今天重新安裝 Docker Desktop 后&#xff0c;發現點擊圖標啟動怎么也沒反應&#xff0c;經過排查后發現換個版本安裝就好了&#xff0c;可能是最新的版…

快速上手c語言

快速上手c語言 快速上手c語言關于學c語言的一些信息雜談第一個C語言程序通過命令行運行c程序Dev-c5.11Visual Studio系列產品 數據類型變量、常量定義變量的方法變量的命名變量的分類變量的使用變量的作用域和生命周期常量 操作符簡單介紹語句選擇語句循環語句 數組數組定義數組…

Nginx核心功能及正則表達

目錄 一&#xff1a;正向代理 1&#xff1a;編譯安裝nginx &#xff08;1&#xff09;安裝支持軟件 &#xff08;2&#xff09;創建運行用戶、組和日志目錄 &#xff08;3&#xff09;編譯安裝nginx &#xff08;4&#xff09;添加nginx系統服務 2&#xff1a;配置正向代…

npm命令介紹(Node Package Manager)(Node包管理器)

文章目錄 npm命令全解析簡介基礎命令安裝npm&#xff08;npm -v檢插版本&#xff09;初始化項目&#xff08;npm init&#xff09;安裝依賴包&#xff08;npm install xxx、npm i xxx&#xff09;卸載依賴包&#xff08;npm uninstall xxx 或 npm uni xxx、npm remove xxx&…

【Linux】Linux基礎概念

一些比較重要的使用Linux的前情提要。 部分經驗來源于網絡&#xff0c;若有侵權請聯系我刪除&#xff0c;主要是做筆記的時候忘記寫來源了&#xff0c;做完筆記很久才寫博客。 專欄目錄&#xff1a;記錄自己的嵌入式學習之路-CSDN博客 目錄 1 Shell命令參數 2 系統變量…

阿里開源Qwen3:大語言模型的新突破

一、模型概覽&#xff1a;豐富的模型家族 Qwen3 系列包含了 2 款混合專家&#xff08;MoE&#xff09;模型與 6 款密集&#xff08;Dense&#xff09;模型&#xff0c;參數量覆蓋范圍極廣&#xff0c;從 0.6B 一直延伸至 235B 。其中&#xff0c;旗艦模型 Qwen3 - 235B - A22B…

數字智慧方案5856丨智慧環保綜合解決方案(50頁PPT)(文末有下載方式)

資料解讀&#xff1a;智慧環保綜合解決方案 詳細資料請看本解讀文章的最后內容。 隨著城市化進程的加速和環境問題的日益嚴峻&#xff0c;智慧環保成為提升城市環境管理水平的重要手段。本文將對智慧環保綜合解決方案進行詳細解讀&#xff0c;探討其在實際應用中的需求、解決…

基于ssm的網盤管理系統(全套)

一、系統架構 前端&#xff1a;vue | element-ui 后端&#xff1a;spring | springmvc | mybatis 環境&#xff1a;jdk1.8 | mysql | maven | tomcat | nodejs 二、代碼及數據庫 三、功能介紹 01. 注冊 02. 登錄 03. 管理員-首頁 04. 管理員-個人中心 …

PostgreSQL 的 VACUUM 與 VACUUM FULL 詳解

PostgreSQL 的 VACUUM 與 VACUUM FULL 詳解 一、基本概念對比 特性VACUUMVACUUM FULL定義常規維護操作&#xff0c;清理死元組激進重組操作&#xff0c;完全重寫表數據鎖級別不阻塞讀寫(共享鎖)排他鎖(阻塞所有操作)空間回收只標記空間為可用&#xff0c;不返還OS空間返還操作…

復刻低成本機械臂 SO-ARM100 舵機配置篇(WSL)

視頻講解&#xff1a; 復刻低成本機械臂 SO-ARM100 舵機配置篇&#xff08;WSL&#xff09; 飛特舵機 組裝之前需要配置舵機的ID&#xff0c;如下的網址為舵機的資料&#xff0c;實際上用不到&#xff0c;但可以mark在這里 Software-深圳飛特模型有限公司 User Guide里面可以…

Tailwind CSS實戰技巧:從核心類到高效開發

使用 Kooboo平臺 訓練實戰技巧&#xff0c;無需配置安裝&#xff0c;直接引入CDN就可以在線練習了&#xff01;具體操作流程&#xff1a;進入Kooboo后&#xff0c;選擇創建空白站點 -> 站點開發 -> 控制面板 -> 頁面 ->新建普通頁面 -> 編寫代碼 一、核心布局類…

【LINUX操作系統】線程操作

了解了線程的基本原理之后&#xff0c;我們來學習線程在C語言官方庫中的寫法與用法。 1. 常見pthread接口及其背后邏輯 1.1 pthread_create 與線程有關的函數構成了?個完整的系列&#xff0c;絕?多數函數的名字都是以“pthread_”打頭的 ? 要使?這些函數庫&#xff0c;…

【AI面試準備】Azure DevOps沙箱實驗全流程詳解

介紹動手實驗&#xff1a;通過 Azure DevOps 沙箱環境實操&#xff0c;體驗從代碼提交到測試篩選的全流程。如何快速掌握&#xff0c;以及在實際工作中如何運用。 通過 Azure DevOps 沙箱環境進行動手實驗&#xff0c;是快速掌握 DevOps 全流程&#xff08;從代碼提交到測試篩選…

VulnHub-DC-2靶機

主機發現 sudo arp-scan -l 以sudo管理員權限掃描本地活動ip地址 Interface: eth0, type: EN10MB, MAC: 08:00:27:22:46:4f, IPv4: 192.168.252.230 Starting arp-scan 1.10.0 with 256 hosts (https://github.com/royhills/arp-scan) 192.168.252.6 4c:5f:70:74:3c:3b …

藏語英語中文機器翻譯入門實踐

&#x1f3af; 項目目標&#xff1a; 輸入藏文句子&#xff0c;自動翻譯成英文和中文&#xff08;或輸入中文&#xff0c;翻譯為英文和藏文&#xff09;。 &#x1f50d; 技術與原理簡介 機器翻譯&#xff08;Machine Translation, MT&#xff09;是人工智能中自然語言處理&a…

【阿里云大模型高級工程師ACP習題集】2.9 大模型應用生產實踐(上篇)

練習題 【單選題】在自然語言處理的法務咨詢場景中,以下哪種模型選擇最為合適? A. 通用大語言模型 B. 經過數學領域微調的模型 C. 面向法律領域訓練的模型 D. 視覺模型 【多選題】以下哪些屬于模型非功能性需求?( ) A. 模型對不同語言的支持能力 B. 模型的響應速度要求 C.…

WPF之ProgressBar控件詳解

文章目錄 1. ProgressBar控件簡介2. ProgressBar的基本屬性和用法2.1 基本屬性2.2 基本用法2.3 代碼中修改進度 3. 確定與不確定模式3.1 確定模式&#xff08;Determinate&#xff09;3.2 不確定模式&#xff08;Indeterminate&#xff09; 4. 在多線程環境中更新ProgressBar4.…

IntelliJ IDEA 保姆級安裝教程(附安裝包)

文章目錄 一、下載二、安裝三、啟動 一、下載 Ultimate 2021.1.1 - Windows x64 (exe) 二、安裝 三、啟動 首次安裝啟動 非首次安裝啟動

Performance API 性能上報

以下是關于 Performance API 性能上報的基本知識點總結: 一、性能監控核心指標體系 1. 關鍵性能指標(Web Vitals) 指標標準采集方式健康閾值LCP (最大內容繪制)測量加載性能PerformanceObserver≤2.5sFID (首次輸入延遲)測量交互響應PerformanceObserver≤100msCLS (累積布…