說說ThreadLocal的實現原理

ThreadLocal是什么?

ThreadLocal是Java中的一個類,用于創建線程局部變量解決線程安全。每個線程都有自己獨立的變量副本,彼此之間互不影響。它的主要作用是在多線程環境下,確保每個線程都有自己的變量實例,避免了變量共享帶來的線程安全問題。

ThreadLocal 的主要功能

  1. 線程局部變量:每個線程都有自己的變量副本,互不干擾。
  2. 線程安全:避免了多線程環境下的競爭和沖突。

ThreadLocal 的核心方法

  • Thread.currentThread()獲取當前線程。
  • getMap(t)獲取當前線程的ThreadLocalMap
  • map.getEntry(this)ThreadLocalMap中獲取以當前ThreadLocal為鍵的條目(Entry)。
  • 如果條目存在,返回其值;否則調用setInitialValue()進行初始化。
set(T value)

set(T value) 方法用于設置當前線程的局部變量值,具體實現如下:

public void set(T value) {Thread t = Thread.currentThread();ThreadLocalMap map = getMap(t);if (map != null)map.set(this, value);elsecreateMap(t, value);
}
  • Thread.currentThread()獲取當前線程。
  • getMap(t)獲取當前線程的ThreadLocalMap
  • 如果ThreadLocalMap存在,調用map.set(this, value)設置值;否則調用createMap(t, value)創建一個新的ThreadLocalMap
remove()

remove() 方法用于移除當前線程的局部變量值,具體實現如下:

public void remove() {ThreadLocalMap m = getMap(Thread.currentThread());if (m != null)m.remove(this);
}
  • 獲取當前線程的ThreadLocalMap
  • 如果ThreadLocalMap存在,調用m.remove(this)移除條目。

ThreadLocal 的典型使用場景

  1. 用戶會話管理:在web應用中,存儲與當前線程(用戶請求)相關的信息,如用戶會話、請求上下文等。
  2. 數據庫連接:為每個線程分配獨立的數據庫連接,避免連接共享帶來的線程安全問題。
  3. 事務管理:存儲與當前線程相關的事務信息,如事務狀態、事務ID等。
  4. 格式化工具:存儲與當前線程相關的工具實例,如SimpleDateFormat,避免工具共享帶來的線程安全問題。

ThreadLocal 的實現原理

ThreadLocal 是 Java 中用于實現線程局部變量的類,它為每個線程提供一個獨立的變量副本。實現這一點的關鍵在于每個線程都有一個 ThreadLocalMap 對象,ThreadLocalMap 類似于一個哈希表,存儲了當前線程所對應的所有 ThreadLocal 變量及其值。理解 ThreadLocal 的實現原理,需要深入探討其核心機制及內部結構。以下是詳細的解釋:

1. ThreadLocal 類的基本結構

ThreadLocal 類本身非常簡單,主要包含以下幾個重要的方法:

  • get(): 獲取當前線程的局部變量值。
  • set(T value): 設置當前線程的局部變量值。
  • remove(): 移除當前線程的局部變量值。

這些方法都依賴于每個線程獨有的 ThreadLocalMap

2. ThreadLocalMap

ThreadLocalMapThreadLocal 的一個靜態內部類,它是一個自定義的哈希表,用于存儲線程局部變量。每個線程都有一個 ThreadLocalMap 實例,存儲在線程對象中。其實現機制如下:

  • 存儲位置ThreadLocalMap存儲在每個線程的Thread對象中,具體來說,ThreadLocalMap是Thread類的一個實例變量:
public class Thread {// 部分源碼省略.../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;// 部分源碼省略...
}
  • 鍵和值ThreadLocalMap的鍵是ThreadLocal對象,值是實際存儲的數據,類型為Object。為了避免ThreadLocal對象的內存泄漏,ThreadLocalMap的鍵使用的是弱引用(WeakReference<ThreadLocal<?>>)。

3. ThreadLocalMap 的內部結構

ThreadLocalMap 是一個自定義的哈希表,主要包含以下結構:

  • EntryThreadLocalMap的內部靜態類,用于存儲鍵值對。鍵是弱引用的ThreadLocal對象,值是實際數據。而Entry是以數組的形式存在,在源碼中的體現就是Entry數組成員變量table(private Entry[] table;),也就是說每個ThreadLocalMap可以保存多個ThreadLocal作為鍵,值可以設置為任意你想與該ThreadLocal進行關聯的值()。
/*** ThreadLocalMap is a customized hash map suitable only for* maintaining thread local values. No operations are exported* outside of the ThreadLocal class. The class is package private to* allow declaration of fields in class Thread.  To help deal with* very large and long-lived usages, the hash table entries use* WeakReferences for keys. However, since reference queues are not* used, stale entries are guaranteed to be removed only when* the table starts running out of space.*/
static class ThreadLocalMap{/*** The entries in this hash map extend WeakReference, using* its main ref field as the key (which is always a* ThreadLocal object).  Note that null keys (i.e. entry.get()* == null) mean that the key is no longer referenced, so the* entry can be expunged from table.  Such entries are referred to* as "stale entries" in the code that follows.*/static class Entry extends WeakReference<ThreadLocal<?>> {/** The value associated with this ThreadLocal. */Object value;Entry(ThreadLocal<?> k, Object v) {super(k);value = v;}}/*** The initial capacity -- MUST be a power of two.*/private static final int INITIAL_CAPACITY = 16;/*** The table, resized as necessary.* table.length MUST always be a power of two.*/private Entry[] table;/*** The number of entries in the table.*/private int size = 0;/*** The next size value at which to resize.*/private int threshold;// 部分源碼省略...
}
  • 哈希沖突處理ThreadLocalMap使用線性探測法解決哈希沖突。當發生哈希沖突時,會依次檢查下一個位置,直到找到空位置或匹配的鍵。
  • 垃圾回收:由于鍵是弱引用,當ThreadLocal對象沒有其他強引用時,會被垃圾回收器回收。此時,ThreadLocalMap的鍵會變成null,但值仍然存在。因此,需要顯式地調用remove()方法或依賴ThreadLocalMap的內部機制來清理這些條目。

4.Thread的成員變量ThreadLocalMap

每個線程 (Thread 對象) 都有一個 ThreadLocalMap 實例。具體來說,Thread 類有一個 ThreadLocalMap類型的threadLocals 字段,用于保存當前線程的 ThreadLocalMap, 也就是負責管理當前線程的變量副本。因為ThreadLocalMap可以保存多個不同的ThreadLocal對象作為鍵,值為任意內容的鍵值對,所以每個線程可以保存多個變量副本,數量上限取決于ThreadLocal對象的個數。

public
class Thread implements Runnable {// 部分源碼省略.../* ThreadLocal values pertaining to this thread. This map is maintained* by the ThreadLocal class. */ThreadLocal.ThreadLocalMap threadLocals = null;/** InheritableThreadLocal values pertaining to this thread. This map is* maintained by the InheritableThreadLocal class.*/ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;// 部分源碼省略...
}

5. 如何為每個線程提供一個獨立的變量副本,實現線程安全?

在多線程環境中, 每個線程操作 ThreadLocal 類時,只影響到自己線程的 ThreadLocalMap 里面的內容,而不會干擾到其他線程的 ThreadLocalMap。 如果應用程序中只使用一個ThreadLocal,那么每個線程內部的 ThreadLocalMap 都保存著相同的 ThreadLocal對象作為鍵,值可以設置成任意內容。如果應用程序中使用了多個ThreadLocal,那么每個線程內部的 ThreadLocalMap 都保存著多個 ThreadLocal對象 作為鍵,值為 每個ThreadLocal對象所關聯對應的內容,每個線程也就保存了多個獨立的變量副本。因此也就實現了每個線程都有自己的獨立變量副本。

接下來我們看看ThreadLocal的set()方法和get()方法源碼

get()方法

/*** Returns the value in the current thread's copy of this* thread-local variable.  If the variable has no value for the* current thread, it is first initialized to the value returned* by an invocation of the {@link #initialValue} method.** @return the current thread's value of this thread-local*/
public T get() {// 獲取當前線程Thread t = Thread.currentThread();// 獲取當前線程的ThreadLocalMapThreadLocalMap map = getMap(t);// 如果當前線程的ThreadLocalMap不為nullif (map != null) {// 從ThreadLocalMap的Entry類型數組table中獲取以當前// ThreadLocal作為鍵的Entry實例ThreadLocalMap.Entry e = map.getEntry(this);if (e != null) {// Entry實例e不為null,返回其value@SuppressWarnings("unchecked")T result = (T)e.value;return result;}}// 給當前線程t的ThreadLocalMap設置初始值return setInitialValue();
}/*** Get the entry associated with key.  This method* itself handles only the fast path: a direct hit of existing* key. It otherwise relays to getEntryAfterMiss.  This is* designed to maximize performance for direct hits, in part* by making this method readily inlinable.** @param  key the thread local object* @return the entry associated with key, or null if no such*/
private Entry getEntry(ThreadLocal<?> key) {// 計算索引值,用于在table中定位Entryint i = key.threadLocalHashCode & (table.length - 1);// 獲取當前位置的EntryEntry e = table[i];// 檢查當前位置的Entry是否有效且其存儲的ThreadLocal對象等于keyif (e != null && e.get() == key) {// 直接命中,返回Entryreturn e;} else {// 未命中或Entry已失效,則通過getEntryAfterMiss進一步處理return getEntryAfterMiss(key, i, e);}
}

set()方法

/*** Sets the current thread's copy of this thread-local variable* to the specified value.  Most subclasses will have no need to* override this method, relying solely on the {@link #initialValue}* method to set the values of thread-locals.** @param value the value to be stored in the current thread's copy of*        this thread-local.
*/
public void set(T value) {// 獲取當前線程Thread t = Thread.currentThread();// 獲取當前線程的ThreadLocalMapThreadLocalMap map = getMap(t);if (map != null)//如果當前線程的ThreadLocalMap不為null// 設置key為當前ThreadLocal的值為valuemap.set(this, value);else//如果當前線程的ThreadLocalMap為nullcreateMap(t, value);//初始化一個ThreadLocalMap,鍵為當前ThreadLocal
}//  .../*** Create the map associated with a ThreadLocal. Overridden in* InheritableThreadLocal.** @param t the current thread* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

6. 內存泄露問題

由于ThreadLocalMap的鍵是弱引用,ThreadLocal對象被回收后,鍵會變成null,但值仍然保留在內存中,導致內存泄露。因此,建議在不再使用 ThreadLocal 時顯式調用 remove() 方法,以確保清理數據。

7. 總結

每個線程在使用 ThreadLocal 類時,操作的是自己線程內部的 ThreadLocalMap,這確保了線程之間的隔離:

  • 獨立性:每個線程擁有自己的 ThreadLocalMap 實例,因此對 ThreadLocal 的操作不會相互干擾。
  • 線程安全:由于每個線程有獨立的 ThreadLocalMap,不存在并發訪問 ThreadLocalMap 的問題,因此操作是線程安全的。

這種設計使得 ThreadLocal 非常適合在多線程環境下使用,用于存儲線程私有的變量,從而避免了線程間的數據共享問題。

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

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

相關文章

Retrofit類型安全的HTTP客戶端庫(json)

簡介 Retrofit是Square公司開發的一個類型安全的HTTP客戶端庫&#xff0c;用于Android和Java平臺&#xff0c;它使得與Web服務的交互變得更加簡單快捷。Retrofit將HTTP API轉換成Java接口&#xff0c;讓你可以用更簡潔的代碼形式調用RESTful API&#xff0c;Android網絡編程重點…

在前端開發過程中如果函數參數很多,該如何精簡

1. 在前端開發過程中如果函數參數很多&#xff0c;該如何精簡 1.1. 對象參數&#xff08;對象字面量&#xff09;&#xff1a;1.2. 默認參數和解構賦值&#xff1a;1.3. 使用類或構造函數&#xff1a;1.4. 利用閉包或者高階函數&#xff1a;1.5. 利用ES6的擴展運算符&#xff1…

【LeetCode】每日一題:反轉鏈表

題解思路 循環的方法需要注意prev應該是None開始&#xff0c;然后到結束的時候prev是tail&#xff0c;遞歸的思路很難繞過彎來&#xff0c;主要在于很難想清楚為什么可以返回尾節點&#xff0c;需要多做遞歸題&#xff0c;以及遞歸過程中&#xff0c;可以不使用尾節點來找當前…

Nuxt3 的生命周期和鉤子函數(二)

title: Nuxt3 的生命周期和鉤子函數&#xff08;二&#xff09; date: 2024/6/26 updated: 2024/6/26 author: cmdragon excerpt: 摘要&#xff1a;本文深入介紹了Nuxt.js框架中幾個關鍵的生命周期鉤子函數&#xff0c;包括app:redirected&#xff08;SSR環境下重定向前觸發…

20240626讓飛凌的OK3588-C開發板在相機使用1080p60分辨率下預覽

20240626讓飛凌的OK3588-C開發板在相機使用1080p60分辨率下預覽 2024/6/26 15:15 4.2.1 全編譯測試 在源碼路徑內&#xff0c;提供了編譯腳本 build.sh&#xff0c;運行該腳本對整個源碼進行編譯&#xff0c;需要在終端切換到解壓 出來的源碼路徑&#xff0c;找到 build.sh 文件…

6.26作業

1.整理思維導圖 2.統計家目錄下.c文件的個數 ls ~/*.c | wc -l 3.終端輸入一個.sh文件&#xff0c;判斷文件是否由可執行權限&#xff0c;如果有可執行權限運行腳本&#xff0c;沒有可執行權限添加可執行權限后&#xff0c;再運行腳本 #!/bin/bash read -p "請輸入一個.…

spring模塊(二)SpringBean(2)InitializingBean

一、介紹 InitializingBean是Spring框架提供的一個接口&#xff0c;用于在Bean初始化完成后執行特定的初始化邏輯。 二、使用 1、使用方法 1.1、實現InitializingBean接口 可以讓Bean實現該接口&#xff0c;并重寫其afterPropertiesSet()方法 1.2、注冊 也即讓bean初始化…

從官方源碼精簡出第1個FreeRTOS程序

一、下載官方源碼 1、打開百度搜索freerots&#xff0c;找到官網:FreeRTOS官網 2、將源碼解壓到沒有中文目錄的路徑下 二、刪減目錄 1、刪除FreeRTOS-Plus和tools 2、刪除FreeRTOS/Demo下除CORTEX_STM32F103_Keil外的所有文件 3、刪除FreeRTOS\Source\portable下除RVDS和MemM…

vue2面試題——API

1. $set this.$set(目標對象target&#xff0c;改的位置&#xff0c;最終數據) /* 數據更新了而視圖沒有更新的情況 */ <template><div>{{ arr }}<button clickbtn>按鈕</button></div> </template> <script> export default {name:…

海康威視攝像頭修復

一、適用場景 1、室外安裝的攝像頭&#xff0c;長時間日曬雨淋后&#xff0c;可能因風向導致雨水進入水晶頭&#xff0c;進而攝像頭無法識別&#xff1b; 2、在經常施工的場地&#xff0c;可能由于車輛的進出&#xff0c;或施工設備的運行導致攝像頭的網線水晶頭斷裂而無法使用…

潯川社團正式啟用 代碼付費制度——潯川總社部

潯川社團正式啟用 代碼付費制度。 規則&#xff1a; 潯川社團源代碼收費標準表&#xff08;1&#xff09; 1-5行代碼0.2元/行1-10行代碼0.3元/行1-20行代碼0.5元/行 潯川社團源代碼收費標準表&#xff08;2&#xff09; 1-30行代碼0.6元/行1-40行代碼0.8元/行1-50行代碼0.09元…

【PythonWeb開發】Flask中間件鉤子函數實現封IP

在 Flask 框架中&#xff0c; 提供了幾種類型的鉤子&#xff08;類似于Django的中間件&#xff09;&#xff0c;它們是在請求的不同階段自動調用的函數。這些鉤子讓你能夠對請求和響應的處理流程進行擴展&#xff0c;而無需修改核心代碼。 Flask鉤子的四種類型 before_first_r…

IT入門知識第八部分《云計算》(8/10)

目錄 云計算&#xff1a;現代技術的新篇章 1. 云計算基礎 1.1 云計算的起源和發展 云計算的早期概念 云計算的發展歷程 1.2 云計算的核心特點 按需自助服務 廣泛的網絡訪問 資源池化 快速彈性 按使用量付費 1.3 云計算的優勢和挑戰 成本效益 靈活性和可擴展性 維…

[leetcode]intersection-of-two-arrays-ii 兩個數組的交集 II

. - 力扣&#xff08;LeetCode&#xff09; class Solution { public:vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {sort(nums1.begin(), nums1.end());sort(nums2.begin(), nums2.end());int length1 nums1.size(), length2 …

動態規劃——123. 買賣股票的最佳時機 III

目錄 1、題目鏈接 2、題目分析 1.狀態表示 2.狀態轉移方程 3.初始化 4.填表 5.返回值 3、代碼解析 1、題目鏈接 123. 買賣股票的最佳時機 III 2、題目分析 1.狀態表示 由題目可知&#xff0c;我們分為兩種狀態&#xff0c;買入和賣出&#xff0c;又因為只能完成兩次交易…

windows下如何配置vs code的編譯環境

在 Windows 上配置 VS Code 的編譯環境涉及安裝編譯器、配置 VS Code 以及編寫和運行代碼。以下是具體的步驟&#xff1a; 步驟 1&#xff1a;安裝必要的軟件 安裝 Visual Studio Code&#xff1a; 訪問 VS Code 的官方網站并下載安裝包。按照安裝向導進行安裝。 安裝 C/C 編譯…

盲源信道分離—FastICA算法性能仿真

本案例中使用Matlab軟件對FastICA算法的聲音分離性能進行了仿真&#xff0c;分別對簡單波形的混合信號、不同類型聲音的混合信號、同一類型的混合信號這三種情況進行仿真&#xff0c;主要從分離信號的波形形狀、串音誤差兩方面對分離性能進行衡量&#xff0c;仿真結果顯示快速I…

Gradle學習-3 Gradle構建的生命周期

Gradle常用文件目錄 Gradle 構建的生命周期&#xff0c;有3個階段: 初始化階段配置階段執行階段 1、初始化階段 Gradle 支持構建單個工程個多個子工程&#xff0c;初始化階段主要負責收集所有參與本次構建的子工程&#xff0c;創建一個項目的層次結構&#xff0c;并未每個…

SpringBoot優點達項目實戰:獲取系統配置接口(三)

SpringBoot優點達項目實戰&#xff1a;獲取系統配置接口&#xff08;二&#xff09; 文章目錄 SpringBoot優點達項目實戰&#xff1a;獲取系統配置接口&#xff08;二&#xff09;1、查看接口2、查看數據庫3、代碼實現1、創建實體類SysConfig2、創建返回數據的vo3、創建control…

【INTEL(ALTERA)】Eclipse Nios II SBT 無法從模板創建新應用程序和 BSP

目錄 說明 解決方法 說明 您應該能夠創建新的應用程序和 BSP 模板包含以下步驟&#xff1a; 選擇 Nios II應用程序和 BSP 來自模板。選擇您的.sopcinfo 文件并選擇模板。從您的工作區單擊 選擇現有的 BSP 項目。單擊 創建。選擇所需的 BSP 選項。單擊 完成。 但是&#xf…