Thread,ThreadLocal,ThreadLocalMap 三者的關系, 以及在實際開發中的應用【AI記錄用】

在 Java 多線程編程中,ThreadThreadLocalThreadLocalMap 是三個緊密相關的類,它們共同構成了 Java 中**線程本地變量(Thread-Local Storage)**機制的基礎。下面我將從 三者的關系實現原理 以及 實際開發中的應用 三個方面進行詳細講解。


🧱 一、三者的關系(結構圖)

Thread└── ThreadLocal.ThreadLocalMap threadLocals(線程私有)└── Entry[] table(鍵值對數組)└── Entry extends WeakReference<ThreadLocal<?>>├── key: ThreadLocal 實例(弱引用)└── value: 線程本地變量值

1.?Thread?類

  • 每個線程(Thread)都有一個私有的屬性:
    ThreadLocal.ThreadLocalMap threadLocals;
  • 這個屬性保存了該線程所有與?ThreadLocal?相關的變量。

2.?ThreadLocal<T>?類

  • 提供線程本地變量的訪問接口。
  • 每個線程通過?ThreadLocal?實例訪問自己的本地變量。
  • 常用方法:
    java

    深色版本

    void set(T value);   // 設置線程本地變量
    T get();             // 獲取線程本地變量
    void remove();       // 移除線程本地變量

3.?ThreadLocalMap?類

  • 是?ThreadLocal?的一個靜態內部類。
  • 本質上是一個自定義的?Map 結構,用于存儲線程本地變量。
  • 鍵(Key)是?ThreadLocal?實例(弱引用),值(Value)是線程本地值
  • 與?HashMap?不同,它不是線性探測,而是開放尋址法(open addressing)。

🔍 二、實現原理詳解

1. 數據結構

  • 每個線程都有自己的?ThreadLocalMap,它保存了所有與該線程綁定的?ThreadLocal?變量。
  • ThreadLocalMap?中的?Entry?是一個?WeakReference<ThreadLocal<?>>,它的?key?是弱引用,value?是強引用。

2.?ThreadLocal.set()?的過程

  1. 獲取當前線程?t = Thread.currentThread()
  2. 獲取線程的?ThreadLocalMap t.threadLocals
  3. 如果存在,則設置?map.set(this, value)
  4. 如果不存在,則創建一個新的?ThreadLocalMap?并設置初始值

3.?ThreadLocal.get()?的過程

  1. 獲取當前線程?t = Thread.currentThread()
  2. 獲取線程的?ThreadLocalMap
  3. 如果存在,查找以?this?為 key 的值并返回
  4. 如果不存在或找不到值,調用?initialValue()?初始化一個默認值(默認為 null)

🧼 三、為什么使用弱引用(WeakReference)?

  • ThreadLocalMap?的 key 是?ThreadLocal?的弱引用,這樣做的目的是為了防止內存泄漏。
  • 如果 key 是強引用,當?ThreadLocal?實例不再被外部引用時,由于線程還持有它的引用,GC 無法回收,導致內存泄漏。
  • 使用弱引用可以讓?ThreadLocal?在沒有外部引用時被回收,但需要注意?value?仍然可能未被清除(需要手動調用?remove())。

📌 四、實際開發中的應用場景

? 1.?用戶上下文傳遞(如登錄信息)

public class UserContext {private static final ThreadLocal<String> currentUser = new ThreadLocal<>();public static void setCurrentUser(String user) {currentUser.set(user);}public static String getCurrentUser() {return currentUser.get();}public static void clear() {currentUser.remove();}
}

使用場景:

  • 在 Web 應用中,每個請求由一個線程處理,可以將當前用戶信息存入?ThreadLocal,避免層層傳遞。
  • 在 AOP、攔截器中設置,業務代碼中直接獲取當前用戶。

? 2.?數據庫事務管理

public class TransactionManager {private static final ThreadLocal<Connection> connectionHolder = new ThreadLocal<>();public static void setConnection(Connection conn) {connectionHolder.set(conn);}public static Connection getConnection() {return connectionHolder.get();}public static void clear() {connectionHolder.remove();}
}

使用場景:

  • 同一線程內多個 DAO 方法共享同一個事務連接。
  • 避免傳遞?Connection?參數,實現事務一致性。

? 3.?日志追蹤 ID(Trace ID)

public class TraceContext {private static final ThreadLocal<String> traceId = new ThreadLocal<>();public static void setTraceId(String id) {traceId.set(id);}public static String getTraceId() {return traceId.get();}public static void clear() {traceId.remove();}
}

使用場景:

  • 在分布式系統中,為每個請求分配一個唯一 Trace ID,記錄在?ThreadLocal?中,方便日志追蹤。
  • 日志框架(如 Logback、Log4j)可以集成該機制,自動打印 Trace ID。

? 4.?避免線程安全問題(替代 synchronized)

某些場景下,可以通過 ThreadLocal 替代加鎖,提升性能。

例如,SimpleDateFormat 是線程不安全的類,可以這樣使用:

private static final ThreadLocal<SimpleDateFormat> sdf = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));// 使用
String dateStr = sdf.get().format(new Date());

?? 五、使用注意事項(避免內存泄漏)

問題建議
不及時調用?remove()導致?value?無法回收,造成內存泄漏
線程池中使用?ThreadLocal線程復用導致舊值殘留,需在任務前后手動清理
使用弱引用的 keykey 會被回收,但 value 仍存在,需配合清理機制

? 線程池中使用?ThreadLocal?的正確方式:

ExecutorService executor = Executors.newFixedThreadPool(5);Runnable task = () -> {try {UserContext.setCurrentUser("user123");// 執行業務邏輯...} finally {UserContext.clear(); // 必須手動清理}
};executor.submit(task);

📊 六、總結:三者關系一覽表

名稱類型作用
Thread每個線程對象,包含一個?ThreadLocalMap
ThreadLocal提供線程本地變量的訪問接口
ThreadLocalMap類(ThreadLocal?的靜態內部類)實際存儲線程本地變量的 Map,鍵為?ThreadLocal,值為變量值

🧩 七、延伸:InheritableThreadLocal

InheritableThreadLocalThreadLocal 的子類,用于實現父子線程之間的本地變量繼承

ThreadLocal<String> threadLocal = new InheritableThreadLocal<>();

使用場景:

  • 創建子線程時繼承父線程的本地變量(如日志上下文、用戶信息等)。

📚 八、推薦工具和庫

工具/庫用途
TransmittableThreadLocal(TTL)阿里開源,解決線程池中 ThreadLocal 傳遞問題
MDC(Mapped Diagnostic Context)Logback/Log4j 中的日志上下文追蹤,底層使用 ThreadLocal
ThreadLocal?+ AOP實現自動上下文管理,如日志、事務等

為什么 用戶上下文傳遞(如登錄信息) 不需要 創建ThreadLocalMap ?

在討論 ThreadLocalThreadThreadLocalMap 三者的關系時,理解它們如何協同工作對于掌握 Java 中線程本地存儲(Thread-Local Storage)的概念至關重要。當你使用 ThreadLocal 來實現用戶上下文傳遞(如登錄信息)時,實際上并不需要你手動創建 ThreadLocalMap,這是因為 ThreadLocal 的內部機制已經為你處理了這個過程。

為什么不需要手動創建?ThreadLocalMap

  1. 自動管理:當你通過 ThreadLocalset() 方法設置一個值時,如果當前線程的 ThreadLocalMap 尚未初始化(即首次調用 set()),ThreadLocal 會自動為該線程創建一個新的 ThreadLocalMap 并將鍵值對插入其中。同樣的,當你調用 get() 方法獲取值時,如果沒有找到對應的 ThreadLocalMap,它也會自動進行初始化。

  2. 封裝性ThreadLocal 類的設計目的是為了提供一種簡單的方式來訪問線程本地變量,而不需要直接操作底層的數據結構(如 ThreadLocalMap)。因此,所有的復雜性都被封裝在了 ThreadLocal 類和它的內部類 ThreadLocalMap 中。

  3. 透明性:開發者只需關注如何使用 ThreadLocal 來存儲和檢索數據,而無需關心如何管理 ThreadLocalMap。這種設計使得代碼更加簡潔,易于理解和維護。

示例解釋

考慮以下示例,用于說明如何使用 ThreadLocal 實現用戶上下文傳遞:

public class UserContext {private static final ThreadLocal<String> currentUser = new ThreadLocal<>();public static void setCurrentUser(String user) {currentUser.set(user); // 如果沒有ThreadLocalMap,則會自動創建}public static String getCurrentUser() {return currentUser.get(); // 自動處理ThreadLocalMap的存在與否}public static void clear() {currentUser.remove(); // 清理資源}
}

在這個例子中,我們定義了一個靜態的 ThreadLocal 變量 currentUser 來存儲每個線程的用戶信息。當我們調用 setCurrentUser() 方法時,ThreadLocal 會檢查當前線程是否有 ThreadLocalMap。如果沒有,它會自動創建一個并將其與當前線程關聯起來。同樣地,getCurrentUser() 方法也會自動處理 ThreadLocalMap 的存在情況,確保能夠正確地返回當前線程的用戶信息。

總結

  • 無需手動創建?ThreadLocalMap:由于?ThreadLocal?的設計目標是簡化線程本地變量的使用,因此所有關于?ThreadLocalMap?的管理工作都是自動完成的。
  • 自動化管理:無論是設置值還是獲取值,ThreadLocal?都會根據需要自動管理其內部的?ThreadLocalMap
  • 面向接口編程:使用者只需要關心如何使用?ThreadLocal?提供的方法來操作線程本地變量,而不必深入理解其實現細節。

這種方式不僅簡化了開發者的任務,還減少了出錯的可能性,使得 ThreadLocal 成為了多線程環境下管理線程局部數據的強大工具。

怎么保證clear()會被執行呢?

這是一個非常關鍵且常見的問題:在實際開發中,我們經常使用 ThreadLocal 存儲線程上下文信息(如用戶信息、事務、日志追蹤 ID 等),但很多人沒有顯式調用 clear(),這就可能導致內存泄漏或上下文污染


🧨 問題本質:ThreadLocal?不清理會怎樣?

? 1.?內存泄漏(Memory Leak)

  • ThreadLocalMap?的?key?是?ThreadLocal?的弱引用(WeakReference),但?value?是強引用。
  • 如果線程執行完任務后,ThreadLocal?被回收(key 為 null),但?value?仍存在?ThreadLocalMap?中。
  • 如果線程是線程池中的線程(長期存活),value?就一直無法被回收,造成內存泄漏。

? 2.?上下文污染(Context Pollution)

  • 在線程池中,線程會被復用。
  • 如果不清理,下一個任務可能訪問到前一個任務的?ThreadLocal?值,導致數據錯亂。
  • 例如:用戶 A 的請求結束后沒有清理登錄信息,下一個用戶 B 的請求可能誤讀為 A 的信息。

? 實際開發中如何保證?clear()?被執行?

? 方法一:在?try-finally?塊中手動清理(最常見)

java

深色版本

void handleRequest(String userId) {UserContext.setCurrentUser(userId);try {// 業務邏輯} finally {UserContext.clear(); // 保證執行}
}

? 優點:簡單直接,適合關鍵業務邏輯
?? 缺點:容易忘記,不適合大規模使用


? 方法二:使用 AOP(如 Spring AOP)自動清理(推薦)

通過 AOP 攔截請求入口,在方法執行前后自動設置和清理 ThreadLocal

示例:Spring AOP + 注解
@Aspect
@Component
public class UserContextAspect {@Around("@annotation(WithUserContext)")public Object withUserContext(ProceedingJoinPoint pjp) throws Throwable {String userId = extractUserId(pjp); // 從參數或請求中獲取用戶IDUserContext.setCurrentUser(userId);try {return pjp.proceed();} finally {UserContext.clear();}}private String extractUserId(ProceedingJoinPoint pjp) {// 實現從參數中提取用戶ID的邏輯return "user123";}
}
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WithUserContext {}
@Service
public class SomeService {@WithUserContextpublic void businessMethod() {// 這里可以直接使用 UserContext.getCurrentUser()}
}

? 優點:統一管理,避免漏掉清理
?? 缺點:需要集成 AOP 框架,配置稍復雜

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

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

相關文章

[故障診斷方向]SNNs:針對小樣本軸承故障診斷的孿生神經網絡模型

目錄 1. ?引言與背景總結? 2. ?方法框架總結? 3. ?訓練策略總結? 4. ?實驗驗證總結? 核心代碼實現&#xff08;PyTorch框架&#xff09; ?1. SNN特征提取器&#xff08;多尺度卷積模塊&#xff09; ?結論與未來工作總結? 1. ?引言與背景總結? ?問題陳述?…

Java中緩存的使用淺講

Java中緩存的使用淺講在Java中&#xff0c;緩存系統的使用對于提升應用性能至關重要。緩存的作用主要是減少訪問慢速存儲&#xff08;如數據庫或文件系統&#xff09;的頻率&#xff0c;從而提高應用的響應速度。以下是對Java中緩存系統的全面講解&#xff0c;包括緩存的類型、…

洛谷 P10264 [GESP202403 八級] 接竹竿 普及+/提高

題目描述 小楊同學想用卡牌玩一種叫做“接竹竿”的游戲。 游戲規則是&#xff1a;每張牌上有一個點數 vvv&#xff0c;將給定的牌依次放入一列牌的末端。若放入之前這列牌中已有與這張牌點數相 同的牌&#xff0c;則小楊同學會將這張牌和點數相同的牌之間的所有牌全部取出隊列&…

windows docker-02-docker 最常用的命令匯總

一、鏡像管理命令說明常用參數示例docker pull <鏡像名>:<標簽>拉取鏡像docker pull nginx:latestdocker images查看本地鏡像docker images -a&#xff08;含中間層鏡像&#xff09;docker rmi <鏡像ID>刪除鏡像docker rmi -f $(docker images -q)&#xff0…

前端react項目目錄詳解

1. 項目根目錄文件??文件/目錄作用??package.json??定義項目依賴、腳本命令&#xff08;如 start/build&#xff09;、版本信息等??.env??基礎環境變量配置&#xff08;所有環境共享&#xff09;??.env.development??開發環境專用變量&#xff08;如本地API地址&…

前端-CSS (樣式引入、選擇器)

文章目錄大綱前端三大件常用樣式顏色px:像素1.CSS三種引入方式1.1 行內樣式1.2 頁內樣式1.3 引入外部樣式表文件&#xff08;常見&#xff09;基礎選擇器1. 標記選擇器2. id選擇器3. 類選擇器 最常用4 * 選擇器 使用頻率較低復合選擇器偽類選擇器1.超鏈接偽類&#xff1a;2.子元…

7月19日 臺風“韋帕“強勢逼近:一場與時間賽跑的防御戰

中央氣象臺7月19日10時繼續發布臺風黃色預警,今年第6號臺風"韋帕"正以每小時20-25公里的速度向西偏北方向移動,強度逐漸加強。這個來自海洋的"不速之客"中心附近最大風力已達10級(25米/秒),預計將于20日下午至夜間在廣東深圳到海南文昌一帶沿海登陸,…

學習 Python 爬蟲需要哪些基礎知識?

學習 Python 爬蟲需要掌握一些基礎技術和概念。 1. Python 基礎語法 這是最根本的前提&#xff0c;需要熟悉&#xff1a; - 變量、數據類型&#xff08;字符串、列表、字典等&#xff09; - 條件判斷、循環語句 - 函數、類與對象 - 模塊和包的使用&#xff08;如 import 語…

IELTS 閱讀C15-Test 2-Passage 2

繼續雅思上分實驗。這次正確率是10/13&#xff0c;還是挺讓我吃驚的&#xff0c;因為我又沒有完全讀懂&#xff01; 題型1-填空題這道題目很簡單&#xff0c;同樣地去原文段落里找就好&#xff0c;最后一個空填錯了是因為我不知道mitigate就是decrease同義詞。 題型2-人物匹配題…

7.18 Java基礎 |

以下內容&#xff0c;參考Java 教程 | 菜鳥教程&#xff0c;下邊是我邊看邊記的內容&#xff0c;以便后續復習使用。 多態&#xff1a; 繼承&#xff0c;接口就是多態的具體體現方式。生物學上&#xff0c;生物體或物質可以具有許多不同的形式或者階段。 多態分為運行時多態&…

網絡安全知識學習總結 Section 11

一、實驗知識總結&#xff08;模擬&#xff09;等價路由配置實驗并抓包分析按流分析實驗拓撲圖&#xff1a;AR1配置&#xff1a;<Huawei>sys [Huawei]int g0/0/0 [Huawei-GigabitEthernet0/0/0]ip address 192.168.1.1 30 [Huawei-GigabitEthernet0/0/0]int g0/0/1 [Huaw…

VBA 運用LISTBOX插件,選擇多個選項,并將選中的選項回車錄入當前選中的單元格

維護好數據&#xff0c;并新增一個activeX列表框插件Private Sub Worksheet_SelectionChange(ByVal Target As Range)If Target.Count > 1 Then Exit SubIf Target.Row > 2 And Target.Row < 10 And Target.Column 2 Then 選擇操作范圍With ListBox1.MultiSelect 1 …

ASP .NET Core 8實現實時Web功能

ASP.NET Core SignalR 是一個開放源代碼庫&#xff0c;可用于簡化向應用添加實時 Web 功能。 實時 Web 功能使服務器端代碼能夠將內容推送到客戶端。以下是 ASP.NET Core SignalR 的一些主要功能&#xff1a;自動處理連接管理同時向所有連接的客戶端發送消息。 例如聊天室向特定…

最新版谷歌瀏覽器 內網安裝 pdf無法預覽

最新版谷歌瀏覽器 內網安裝 pdf無法預覽 谷歌下載地址 谷歌下載地址 不同的瀏覽器版本&#xff0c;兼容的js標準不一樣 js標準也在不斷升級&#xff0c;增加新的方法。

NX二次開發常用函數坐標轉化UF_MTX4_csys_to_csys和UF_MTX4_vec3_multipl

一、UF_MTX4_csys_to_csys 1.1 函數名稱 UF_MTX4_csys_to_csys1.2 函數中各參數解釋&#xff1a;函數參數解釋&#xff1a; 第1個參數為輸入&#xff1a; 輸入const double 雙精度類型的參數&#xff0c;參數的變量格式為from_origin [ 3 ]&#xff0c;坐標系&#xff…

JAVA中的Collections 類

文章目錄前言一、 排序方法 sort() 和 reverseOrder()1. sort(List<T> list)2.sort(List<T> list, Comparator<? super T> c)二、查找方法 max(), min()1.max(Collection<? extends T> coll)2.min(Collection<? extends T> coll)3.max(Collec…

統計學習方法

一、統計學習方法步驟 得到一個有限的訓練數據集合確定學習模型的集合-假設空間確定模型選擇的準則-策略實現求解最優模型的算法-算法通過學習方法選擇最優模型利用學習的最優模型對新數據進行預測或分析 二、統計學習方法分類 三、統計學習的基本分類&#xff08;監督學習&a…

windows docker-01-desktop install windows10 + wls2 啟用

windows10 安裝 docker 版本信息確認 需要區分 windows 是 amd64 還是 arm64 powershell 中執行&#xff1a; > echo $env:PROCESSOR_ARCHITECTURE AMD64下載 官方 https://www.docker.com/products/docker-desktop/ 下載 windows amd64 下載好了直接安裝。 如何驗證…

Elasticsearch集群出現腦裂(Split-Brain)如何排查原因和處理?

Elasticsearch集群出現腦裂(Split-Brain)如何排查原因和處理? 1. 腦裂(Split-Brain)背景 定義:腦裂是指 Elasticsearch 集群由于網絡分區(network partition)或其他原因分裂成多個獨立的子集群,每個子集群認為自己是主集群,導致不同的子集群可能獨立處理請求,造成數…

Apache Ignite 的 Pages Writes Throttling(頁面寫入節流)

&#x1f31f; 一、什么是 Checkpointing&#xff08;檢查點機制&#xff09;&#xff1f; 在 Apache Ignite 中&#xff1a; 數據是先保存在內存中&#xff08;RAM&#xff09;&#xff0c;然后異步寫入磁盤。當數據被修改時&#xff0c;它首先被更新在內存中的“頁”上&#…