面試 ThreadLocal,被問(wèn)懵了?看完這篇文章你就穩(wěn)了!
引言
小米最近在做社招面試,遇到了一位候選人,面試到 Java 并發(fā)時(shí),聊到了 ThreadLocal,候選人一開(kāi)始信心滿滿:“這個(gè)我會(huì)!” 結(jié)果,深挖幾輪之后,發(fā)現(xiàn)這位候選人只會(huì)皮毛,甚至踩了不少坑……
于是,今天就和大家聊聊 ThreadLocal 這個(gè)面試高頻考點(diǎn),不僅讓你能答出基礎(chǔ)概念,還能講出實(shí)際使用場(chǎng)景,讓面試官對(duì)你刮目相看!
什么是 ThreadLocal?
想象一下,你去健身房辦了一張私教卡,每次去健身房,教練都會(huì)給你專屬定制的訓(xùn)練計(jì)劃,而不會(huì)讓你去練別人的計(jì)劃。
ThreadLocal 就像這張 私教卡,它的核心作用就是:讓每個(gè)線程都能擁有自己的專屬變量,而不會(huì)影響到其他線程。
在 Java 代碼中,我們通常用 ThreadLocal 來(lái) 存儲(chǔ)每個(gè)線程獨(dú)有的數(shù)據(jù),避免線程之間的數(shù)據(jù)污染。來(lái)看一個(gè)最簡(jiǎn)單的例子:
圖片
運(yùn)行結(jié)果:
圖片
每個(gè)線程都有自己獨(dú)立的 ThreadLocal 變量,即使修改了變量的值,也不會(huì)影響其他線程!
ThreadLocal 的工作原理
1. 底層數(shù)據(jù)結(jié)構(gòu)
ThreadLocal 的底層實(shí)現(xiàn)其實(shí)是 每個(gè)線程內(nèi)部維護(hù)一個(gè) ThreadLocalMap,這個(gè) Map 以 ThreadLocal 變量為 key,具體的值為 value。
簡(jiǎn)單來(lái)說(shuō),每個(gè)線程內(nèi)部都有一個(gè)類似這樣的數(shù)據(jù)結(jié)構(gòu):
圖片
當(dāng)我們調(diào)用 threadLocal.set(value) 時(shí),數(shù)據(jù)并不會(huì)存儲(chǔ)到 ThreadLocal 對(duì)象本身,而是存放在 當(dāng)前線程的 ThreadLocalMap 里。
2. 內(nèi)存泄漏問(wèn)題
ThreadLocal 設(shè)計(jì)上是弱引用,但 ThreadLocalMap 里的 value 是強(qiáng)引用,如果不手動(dòng)清理 ThreadLocal.remove(),可能會(huì)導(dǎo)致內(nèi)存泄漏。來(lái)看一個(gè)坑:
圖片
如果線程池中線程復(fù)用,ThreadLocal 沒(méi)有 remove(),線程的 ThreadLocalMap 可能無(wú)法被回收,從而導(dǎo)致內(nèi)存泄漏。
最佳實(shí)踐: 每次使用完 ThreadLocal,記得調(diào)用 remove(),防止內(nèi)存泄漏!
ThreadLocal 典型使用場(chǎng)景
1. 用戶身份信息存儲(chǔ)(常見(jiàn))
在 Web 應(yīng)用中,每個(gè)請(qǐng)求通常都有自己的 用戶身份信息,比如 登錄用戶 ID。我們可以用 ThreadLocal 來(lái)存儲(chǔ)用戶信息,保證在同一個(gè)線程的多個(gè)方法調(diào)用中,都能訪問(wèn)到當(dāng)前用戶的信息。
圖片
使用方式:
圖片
因?yàn)?HTTP 請(qǐng)求是多線程并發(fā)的,如果使用全局變量存儲(chǔ) userId,會(huì)導(dǎo)致數(shù)據(jù)污染!但用 ThreadLocal,每個(gè)請(qǐng)求的 userId 只存儲(chǔ)在自己的線程中,互不影響。
2. 事務(wù)管理(數(shù)據(jù)庫(kù)連接)
在 Spring 的事務(wù)管理中,ThreadLocal 被用來(lái)存儲(chǔ) 數(shù)據(jù)庫(kù)連接,保證同一個(gè)事務(wù)中使用同一個(gè)數(shù)據(jù)庫(kù)連接。
3. 日志跟蹤(Tracing)
在分布式系統(tǒng)中,我們經(jīng)常需要給每個(gè)請(qǐng)求分配一個(gè)唯一的追蹤 ID(Trace ID),用來(lái)跟蹤整個(gè)請(qǐng)求的執(zhí)行流程。ThreadLocal 也是一個(gè)很好的選擇:
圖片
然后在日志中加上 Trace ID:
圖片
這樣,整個(gè)請(qǐng)求在不同的日志中都有相同的 Trace ID,方便排查問(wèn)題!
ThreadLocal 的優(yōu)缺點(diǎn)
總結(jié)
ThreadLocal 是 Java 并發(fā)中的 “線程局部變量”,常用于存儲(chǔ)線程獨(dú)有的數(shù)據(jù),避免線程間的數(shù)據(jù)污染。它的常見(jiàn)使用場(chǎng)景包括:
- 用戶身份信息存儲(chǔ)
- 事務(wù)管理(數(shù)據(jù)庫(kù)連接)
- 日志追蹤(Tracing)
但使用時(shí)一定要注意內(nèi)存泄漏問(wèn)題,記得在適當(dāng)?shù)臅r(shí)機(jī)調(diào)用 remove() 方法。