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

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

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

前言

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

一、ThreadLocal 解決什么問題?

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

最常見的場景:

// 每個請求一個線程,每個線程需要知道當(dāng)前登錄用戶是誰
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 中攔截請求,把用戶信息 set 進去;Service 層任意位置直接 get,不需要一層層傳參數(shù)。請求結(jié)束 remove 掉。

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

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

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

// Thread 類的源碼
public class Thread {
    ThreadLocal.ThreadLocalMap threadLocals = null;
}

每個 Thread 都有一個 `ThreadLocalMap`,這個 Map 的 key 是 ThreadLocal 實例,value 是你存的值。

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

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

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

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

ThreadLocalMap 是 ThreadLocal 的靜態(tài)內(nèi)部類,不是 java.util.HashMap,它自己實現(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ù)組
}

兩個關(guān)鍵點:

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

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

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

這是面試最愛問的部分。

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

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

正常情況下,棧中的引用和 Entry 的弱引用同時指向 ThreadLocal 對象。

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

棧中的引用(已回收)     ThreadLocal 對象 ← Entry.key(弱引用,GC 后變 null)
                                              Entry.value → 實際數(shù)據(jù)(強引用,還在?。?/p>

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

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

4.2 ThreadLocal 的自我清理機制

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

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

4.3 正確用法:一定要 remove

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

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

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

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

4.4 為什么 key 要用弱引用?

面試官可能會追問這個。

如果 key 是強引用,那即使外部不再使用某個 ThreadLocal 對象,Entry 的 key 也會阻止它被 GC。這樣 ThreadLocal 對象本身也會泄漏。

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

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

五、實際使用場景

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

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,請求結(jié)束 remove。Spring 的 `RequestContextHolder` 就是這么做的。

場景 2:SimpleDateFormat 線程安全問題

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

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

每個線程一份 SimpleDateFormat 實例,既線程安全又不浪費。

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

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

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

六、面試回答模板

- ThreadLocal 是線程本地變量,讓每個線程擁有自己獨立的變量副本。

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

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

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

七、高頻追問

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

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

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

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

相關(guān)文章

  • Java中的SynchronousQueue阻塞隊列使用代碼實例

    Java中的SynchronousQueue阻塞隊列使用代碼實例

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

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

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

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

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

    spring?boot自動裝配之@ComponentScan注解用法詳解

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

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

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

    mybatisplus如何在xml的連表查詢中使用queryWrapper

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

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

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

    Spring?AOP通知類型與實戰(zhàn)示例講解

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

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

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

    Java和JVM的重載識別,重寫方法是怎樣進行的

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

最新評論

龙陵县| 嘉定区| 通道| 常州市| 衡山县| 新龙县| 宕昌县| 和林格尔县| 公安县| 呼图壁县| 南靖县| 上虞市| 满城县| 喀什市| 邵阳县| 宣城市| 沁水县| 德保县| 图木舒克市| 五河县| 博乐市| 尉氏县| 剑川县| 介休市| 长垣县| 万山特区| 昆山市| 张家界市| 津南区| 潮州市| 柯坪县| 靖宇县| 上杭县| 南漳县| 富源县| 泽库县| 高雄县| 崇文区| 南皮县| 霍林郭勒市| 高密市|