最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

一文講透Java面試高頻之ThreadLocal原理、內(nèi)存泄漏、使用場(chǎng)景

 更新時(shí)間:2026年04月14日 09:18:01   作者:紅云夢(mèng)  
在Java多線程開(kāi)發(fā)中,ThreadLocal是高頻使用的線程安全工具,它能為每個(gè)線程創(chuàng)建獨(dú)立的變量副本,完美解決多線程資源共享的并發(fā)問(wèn)題,這篇文章主要介紹了Java面試高頻之ThreadLocal原理、內(nèi)存泄漏、使用場(chǎng)景的相關(guān)資料,需要的朋友可以參考下

前言

ThreadLocal 是 Java 面試中高級(jí)崗位的高頻考點(diǎn)。很多人知道它是"線程本地變量",但面試官一追問(wèn)"為什么會(huì)內(nèi)存泄漏"就答不上來(lái)了。本文從原理到源碼到實(shí)際應(yīng)用,幫你把 ThreadLocal 徹底搞懂。

一、ThreadLocal 解決什么問(wèn)題?

一句話:讓每個(gè)線程擁有自己獨(dú)立的變量副本,線程之間互不干擾。

最常見(jiàn)的場(chǎng)景:

// 每個(gè)請(qǐng)求一個(gè)線程,每個(gè)線程需要知道當(dāng)前登錄用戶(hù)是誰(shuí)
public class UserContext {
    private static final ThreadLocal<User> currentUser = new ThreadLocal<>();
    public static void set(User user) { currentUser.set(user); }
    public static User get() { return currentUser.get(); }
    public static void remove() { currentUser.remove(); }
}

Filter 中攔截請(qǐng)求,把用戶(hù)信息 set 進(jìn)去;Service 層任意位置直接 get,不需要一層層傳參數(shù)。請(qǐng)求結(jié)束 remove 掉。

不用 ThreadLocal 的話怎么辦?要么把 User 對(duì)象從 Controller 傳到 Service 再傳到 DAO(侵入性強(qiáng)),要么放在一個(gè)全局 Map 里自己管理線程安全(復(fù)雜、容易出 bug)。

二、底層原理:Thread、ThreadLocalMap、Entry

很多人以為數(shù)據(jù)存在 ThreadLocal 對(duì)象里。不是。數(shù)據(jù)存在 Thread 對(duì)象里。

// Thread 類(lèi)的源碼
public class Thread {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

每個(gè) Thread 都有一個(gè) `ThreadLocalMap`,這個(gè) Map 的 key 是 ThreadLocal 實(shí)例,value 是你存的值。

調(diào)用關(guān)系:

threadLocal.set(value)
  → 獲取當(dāng)前線程 Thread.currentThread()
  → 拿到該線程的 ThreadLocalMap
  → 以 this(當(dāng)前 ThreadLocal 實(shí)例)為 key,存入 value
threadLocal.get()
  → 獲取當(dāng)前線程 Thread.currentThread()
  → 拿到該線程的 ThreadLocalMap
  → 以 this 為 key,取出 value

關(guān)鍵理解:ThreadLocal 本身不存數(shù)據(jù),它只是一個(gè)"鑰匙",真正的數(shù)據(jù)存在每個(gè)線程自己的 Map 里。 不同線程用同一個(gè) ThreadLocal 對(duì)象作為 key,但各自的 Map 是獨(dú)立的,所以取到的值不同。

三、ThreadLocalMap 的結(jié)構(gòu)

ThreadLocalMap 是 ThreadLocal 的靜態(tài)內(nèi)部類(lèi),不是 java.util.HashMap,它自己實(shí)現(xiàn)了一套:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        Object value;
        Entry(ThreadLocal<?> k, Object v) {
            super(k);  // key 是弱引用
            value = v;
        }
    }
    private Entry[] table;  // 底層數(shù)組
}

兩個(gè)關(guān)鍵點(diǎn):

(1)底層是數(shù)組,不是鏈表/紅黑樹(shù)。哈希沖突用**開(kāi)放尋址法**(線性探測(cè)),不是拉鏈法

(2)key是弱引用(WeakReference)——這就是內(nèi)存泄漏問(wèn)題的根源

四、內(nèi)存泄漏:為什么會(huì)泄漏?怎么避免?

這是面試最?lèi)?ài)問(wèn)的部分。

4.1 弱引用導(dǎo)致的問(wèn)題

棧中的引用(強(qiáng)引用)→ ThreadLocal 對(duì)象 ← Entry.key(弱引用)
                                              Entry.value → 實(shí)際數(shù)據(jù)(強(qiáng)引用)

正常情況下,棧中的引用和 Entry 的弱引用同時(shí)指向 ThreadLocal 對(duì)象。

當(dāng)棧中的引用被回收了(比如方法結(jié)束、變量置 null):

棧中的引用(已回收)     ThreadLocal 對(duì)象 ← Entry.key(弱引用,GC 后變 null)
                                              Entry.value → 實(shí)際數(shù)據(jù)(強(qiáng)引用,還在!)

GC 會(huì)回收只有弱引用指向的 ThreadLocal 對(duì)象,導(dǎo)致 Entry 的 key 變成 null。但 value 是強(qiáng)引用,不會(huì)被回收。

這時(shí)候 Entry 就變成了一個(gè)"key 為 null,value 還在"的廢棄節(jié)點(diǎn)。只要線程還活著(比如線程池中的線程),這個(gè) value 就永遠(yuǎn)不會(huì)被回收——這就是內(nèi)存泄漏。

4.2 ThreadLocal 的自我清理機(jī)制

ThreadLocal 在 get/set/remove 時(shí),會(huì)順便清理 key 為 null 的廢棄 Entry(源碼中叫 expungeStaleEntry)。

所以如果你一直在調(diào)用 ThreadLocal 的方法,廢棄 Entry 會(huì)被逐步清理。但如果你 set 完就再也不碰了,廢棄 Entry 就一直留著。

4.3 正確用法:一定要 remove

try {
    threadLocal.set(value);
    // 業(yè)務(wù)邏輯
} finally {
    threadLocal.remove();  // 必須!
}

特別是在線程池環(huán)境下(Web 應(yīng)用幾乎都是線程池),線程會(huì)被復(fù)用,如果不 remove:

- 下一個(gè)請(qǐng)求可能拿到上一個(gè)請(qǐng)求的數(shù)據(jù)(數(shù)據(jù)串了)

- 廢棄 Entry 越積越多(內(nèi)存泄漏)

4.4 為什么 key 要用弱引用?

面試官可能會(huì)追問(wèn)這個(gè)。

如果 key 是強(qiáng)引用,那即使外部不再使用某個(gè) ThreadLocal 對(duì)象,Entry 的 key 也會(huì)阻止它被 GC。這樣 ThreadLocal 對(duì)象本身也會(huì)泄漏。

用弱引用至少能保證 ThreadLocal 對(duì)象可以被回收,泄漏的只是 value。而且 ThreadLocal 的自我清理機(jī)制可以逐步回收這些 value。

弱引用是一種"盡力而為"的兜底策略,不是完美方案。真正的保障還是要靠手動(dòng) remove。

五、實(shí)際使用場(chǎng)景

場(chǎng)景 1:Web 應(yīng)用中傳遞用戶(hù)上下文

public class RequestContext {
    private static final ThreadLocal<Long> userId = new ThreadLocal<>();
    private static final ThreadLocal<String> traceId = new ThreadLocal<>();
    // set/get/remove 方法...
}

在 Filter 或 Interceptor 中 set,業(yè)務(wù)代碼中 get,請(qǐng)求結(jié)束 remove。Spring 的 `RequestContextHolder` 就是這么做的。

場(chǎng)景 2:SimpleDateFormat 線程安全問(wèn)題

SimpleDateFormat 不是線程安全的。要么每次 new(浪費(fèi)),要么加鎖(性能差),要么用 ThreadLocal:

private static final ThreadLocal<SimpleDateFormat> sdf =
    ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

每個(gè)線程一份 SimpleDateFormat 實(shí)例,既線程安全又不浪費(fèi)。

(當(dāng)然 JDK 8+ 建議直接用 `DateTimeFormatter`,它本身就是線程安全的。)

場(chǎng)景 3:數(shù)據(jù)庫(kù)連接 / 事務(wù)管理

Spring 的事務(wù)管理器用 ThreadLocal 存儲(chǔ)當(dāng)前線程的數(shù)據(jù)庫(kù)連接,保證同一個(gè)事務(wù)中的多次 SQL 操作用的是同一個(gè) Connection。

六、面試回答模板

- ThreadLocal 是線程本地變量,讓每個(gè)線程擁有自己獨(dú)立的變量副本。

- 底層實(shí)現(xiàn):每個(gè) Thread 對(duì)象里有一個(gè) ThreadLocalMap,key 是 ThreadLocal 實(shí)例(弱引用),value 是實(shí)際的值。調(diào)用 set/get 時(shí),先拿到當(dāng)前線程的 Map,再以 ThreadLocal 自身為 key 進(jìn)行操作。

- 關(guān)于內(nèi)存泄漏:因?yàn)?key 是弱引用,當(dāng)外部不再持有 ThreadLocal 的強(qiáng)引用時(shí),GC 會(huì)回收 key,導(dǎo)致 Entry 的 key 變成 null 但 value 還在。特別是在線程池環(huán)境下,線程不會(huì)銷(xiāo)毀,這些廢棄 Entry 就一直占著內(nèi)存。所以用完必須調(diào)用 remove。

- 常見(jiàn)使用場(chǎng)景:Web 應(yīng)用中傳遞用戶(hù)上下文、解決 SimpleDateFormat 線程安全問(wèn)題、Spring 事務(wù)管理器存儲(chǔ)當(dāng)前連接等。

七、高頻追問(wèn)

- ThreadLocalMap 為什么用開(kāi)放尋址法而不是拉鏈法? → ThreadLocalMap 的元素通常很少(一個(gè)線程不會(huì)有太多 ThreadLocal 變量),開(kāi)放尋址法在元素少時(shí)緩存命中率更高,性能更好

- InheritableThreadLocal 是什么? → 子線程可以繼承父線程的 ThreadLocal 值。但在線程池中不適用,因?yàn)榫€程是復(fù)用的不是新建的。阿里的 TransmittableThreadLocal 解決了這個(gè)問(wèn)題

- ThreadLocal 和 synchronized 的區(qū)別? → synchronized 是多個(gè)線程競(jìng)爭(zhēng)同一個(gè)資源,用鎖保證同一時(shí)刻只有一個(gè)線程訪問(wèn);ThreadLocal 是每個(gè)線程一份副本,用空間換時(shí)間,根本不存在競(jìng)爭(zhēng)

到此這篇關(guān)于Java面試高頻之ThreadLocal原理、內(nèi)存泄漏、使用場(chǎng)景的文章就介紹到這了,更多相關(guān)Java ThreadLocal原理、內(nèi)存泄漏內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • Java中的SynchronousQueue阻塞隊(duì)列使用代碼實(shí)例

    Java中的SynchronousQueue阻塞隊(duì)列使用代碼實(shí)例

    這篇文章主要介紹了Java中的SynchronousQueue阻塞隊(duì)列使用代碼實(shí)例,SynchronousQueue是無(wú)緩沖區(qū)的阻塞隊(duì)列,即不能直接向隊(duì)列中添加數(shù)據(jù),會(huì)報(bào)隊(duì)列滿異常,需要的朋友可以參考下
    2023-12-12
  • Spring狀態(tài)機(jī)的使用解讀

    Spring狀態(tài)機(jī)的使用解讀

    本文詳細(xì)介紹了Spring狀態(tài)機(jī)的基本概念、核心組件以及在生產(chǎn)環(huán)境中的應(yīng)用,通過(guò)訂單系統(tǒng)和審批工作流系統(tǒng)的例子,展示了狀態(tài)機(jī)如何簡(jiǎn)化狀態(tài)流轉(zhuǎn)處理,此外,還介紹了狀態(tài)監(jiān)聽(tīng)器、Guards、可視化、分布式狀態(tài)一致性、性能優(yōu)化等技巧,并總結(jié)了Spring狀態(tài)機(jī)的優(yōu)勢(shì)
    2026-01-01
  • 如何通過(guò)java將doc文件轉(zhuǎn)換為docx文件詳解

    如何通過(guò)java將doc文件轉(zhuǎn)換為docx文件詳解

    在數(shù)字化時(shí)代文檔處理成為了我們?nèi)粘9ぷ骱蛯W(xué)習(xí)中不可或缺的一部分,其中doc和docx作為兩種常見(jiàn)的文檔格式,各自具有不同的特點(diǎn)和優(yōu)勢(shì),這篇文章主要給大家介紹了關(guān)于如何通過(guò)java將doc文件轉(zhuǎn)換為docx文件的相關(guān)資料,需要的朋友可以參考下
    2024-07-07
  • spring?boot自動(dòng)裝配之@ComponentScan注解用法詳解

    spring?boot自動(dòng)裝配之@ComponentScan注解用法詳解

    @ComponentScan的作用就是根據(jù)定義的掃描路徑,把符合掃描規(guī)則的類(lèi)裝配到spring容器中,下面這篇文章主要給大家介紹了關(guān)于spring?boot自動(dòng)裝配之@ComponentScan注解用法的相關(guān)資料,需要的朋友可以參考下
    2023-04-04
  • Spring Cloud接口突然變慢的解決方案

    Spring Cloud接口突然變慢的解決方案

    在Spring Cloud項(xiàng)目中,接口突然變慢可能是由多種原因造成的,本文給大家介紹了一些可能的原因以及相應(yīng)的解決方案,通過(guò)代碼示例給大家講解的非常詳細(xì),需要的朋友可以參考下
    2024-01-01
  • mybatisplus如何在xml的連表查詢(xún)中使用queryWrapper

    mybatisplus如何在xml的連表查詢(xún)中使用queryWrapper

    這篇文章主要介紹了mybatisplus如何在xml的連表查詢(xún)中使用queryWrapper,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-01-01
  • IDEA代碼規(guī)范&質(zhì)量檢查的實(shí)現(xiàn)

    IDEA代碼規(guī)范&質(zhì)量檢查的實(shí)現(xiàn)

    這篇文章主要介紹了IDEA代碼規(guī)范&質(zhì)量檢查的實(shí)現(xiàn),文中通過(guò)圖文介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • Spring?AOP通知類(lèi)型與實(shí)戰(zhàn)示例講解

    Spring?AOP通知類(lèi)型與實(shí)戰(zhàn)示例講解

    Spring?AOP提供了五種通知類(lèi)型:@Before、@After、@AfterReturning、@AfterThrowing和@Around,每種通知類(lèi)型都有其特定的使用場(chǎng)景和實(shí)現(xiàn)方式,通過(guò)合理使用這些通知類(lèi)型,可以實(shí)現(xiàn)各種橫切關(guān)注點(diǎn)的模塊化和解耦,感興趣的朋友跟隨小編一起看看吧
    2024-11-11
  • 自定義log4j2中的Appender來(lái)獲取日志內(nèi)容的示例代碼

    自定義log4j2中的Appender來(lái)獲取日志內(nèi)容的示例代碼

    在 Log4j2 中,Appender 是負(fù)責(zé)將日志事件輸出到目標(biāo)地點(diǎn)的組件,本文講述的是通過(guò) log4j 中自定義的 Appender 來(lái)獲取需要打印的日志信息,文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下
    2024-02-02
  • Java和JVM的重載識(shí)別,重寫(xiě)方法是怎樣進(jìn)行的

    Java和JVM的重載識(shí)別,重寫(xiě)方法是怎樣進(jìn)行的

    這篇文章主要介紹了Java和JVM的重載識(shí)別,重寫(xiě)方法是怎樣進(jìn)行的,違章圍繞了Java和JVM的重載識(shí)別,重寫(xiě)方法展開(kāi)相關(guān)資料,需要的小伙伴可以參考一下,希望對(duì)你的工作或?qū)W習(xí)有所幫助
    2022-01-01

最新評(píng)論

崇阳县| 察雅县| 辽宁省| 土默特左旗| 衡南县| 乌兰察布市| 平乡县| 景泰县| 修文县| 平利县| 抚远县| 海丰县| 临沭县| 安福县| 东山县| 丹寨县| 滕州市| 永登县| 军事| 宁明县| 中方县| 浮山县| 万州区| 洛浦县| 溆浦县| 资兴市| 沽源县| 正阳县| 班玛县| 加查县| 元谋县| 体育| 星座| 广河县| 北宁市| 邹城市| 浦东新区| 宁波市| 乌拉特中旗| 清涧县| 绵竹市|