面試官:談?wù)勀銓?duì)ThreadLocal的理解?
大家好,我是陌溪
ThreadLocal 作為 Java 面試的高頻題,陌溪在之前面試的時(shí)候也遇到過(guò),所以后面專門針對(duì) ThreadLocal 寫了一份筆記,讓我們一起來(lái)看看~
什么是ThreadLocal?
從 Java 官方文檔中的描述:ThreadLocal 類用來(lái)提供線程內(nèi)部的局部變量。這種變量在多線程環(huán)境下訪問(wèn)(通過(guò)get 和 set 方法訪問(wèn))時(shí)能保證各個(gè)線程的變量相對(duì)獨(dú)立于其他線程內(nèi)的變量。ThreadLocal?實(shí)例通常來(lái)說(shuō)都是?private static 類型的,用于關(guān)聯(lián)線程和線程上下文。
我們可以得知 ThreadLocal 的作用是:提供線程內(nèi)的局部變量,不同的線程之間不會(huì)相互干擾,這種變量在線程的生命周期內(nèi)起作用,減少同一個(gè)線程內(nèi)多個(gè)函數(shù)或組件之間一些公共變量傳遞的復(fù)雜度。
線程并發(fā):在多線程并發(fā)的場(chǎng)景下
傳遞數(shù)據(jù):我們可以通過(guò) ThreadLocal 在同一線程,不同組件之間傳遞公共變量(有點(diǎn)類似于 Session?)
線程隔離:每個(gè)線程的變量都是獨(dú)立的,不會(huì)互相影響
基本使用
在介紹 ThreadLocal 使用之前,我們首先認(rèn)識(shí)幾個(gè) ThreadLocal 的常見方法
| 方法聲明 | 描述 |
|---|---|
| ThreadLocal() | 創(chuàng)建ThreadLocal對(duì)象 |
| public void set(T value) | 設(shè)置當(dāng)前線程綁定的局部變量 |
| public T get() | 獲取當(dāng)前線程綁定的局部變量 |
| public void remove() | 移除當(dāng)前線程綁定的局部變量 |
使用案例
我們來(lái)看下面這個(gè)線程不安全的案例,感受一下 ThreadLocal 線程隔離的特點(diǎn)。
/**
?*?需求:線程隔離
?*?在多線程并發(fā)的場(chǎng)景下,每個(gè)線程中的變量都是相互獨(dú)立的
?*?線程A:設(shè)置變量1,獲取變量2
?*?線程B:設(shè)置變量2,獲取變量2
?*?@author:?陌溪
?*/
public?class?MyDemo01?{
????//?變量
????private?String?content;
????public?String?getContent()?{
????????return?content;
????}
????public?void?setContent(String?content)?{
????????this.content?=?content;
????}
????public?static?void?main(String[]?args)?{
????????MyDemo01?myDemo01?=?new?MyDemo01();
????????for?(int?i?=?0;?i?5;?i++)?{
????????????new?Thread(()?->?{
????????????????myDemo01.setContent(Thread.currentThread().getName()?+?"的數(shù)據(jù)");
????????????????System.out.println("-----------------------------------------");
????????????????System.out.println(Thread.currentThread().getName()?+?"\t??"?+?myDemo01.getContent());
????????????},?String.valueOf(i)).start();
????????}
????}
}
運(yùn)行后的效果
-----------------------------------------
-----------------------------------------
-----------------------------------------
3??????4的數(shù)據(jù)
-----------------------------------------
2??????4的數(shù)據(jù)
-----------------------------------------
1??????4的數(shù)據(jù)
4??????4的數(shù)據(jù)
0??????4的數(shù)據(jù)
從上面我們可以看到,出現(xiàn)了線程不隔離的問(wèn)題,也就是線程1取出了線程4的內(nèi),那么如何解決呢?
這個(gè)時(shí)候就可以用到 ThreadLocal 了,我們通過(guò) set 將變量綁定到當(dāng)前線程中,然后 get 獲取當(dāng)前線程綁定的變量
/**
?*?需求:線程隔離
?*?在多線程并發(fā)的場(chǎng)景下,每個(gè)線程中的變量都是相互獨(dú)立的
?*?線程A:設(shè)置變量1,獲取變量2
?*?線程B:設(shè)置變量2,獲取變量2
?*?@author:?陌溪
?*/
public?class?MyDemo01?{
????//?變量
????private?String?content;
????public?String?getContent()?{
????????return?content;
????}
????public?void?setContent(String?content)?{
????????this.content?=?content;
????}
????public?static?void?main(String[]?args)?{
????????MyDemo01?myDemo01?=?new?MyDemo01();
????????ThreadLocal?threadLocal?=?new?ThreadLocal<>();
????????for?(int?i?=?0;?i?5;?i++)?{
????????????new?Thread(()?->?{
????????????????threadLocal.set(Thread.currentThread().getName()?+?"的數(shù)據(jù)");
????????????????System.out.println("-----------------------------------------");
????????????????System.out.println(Thread.currentThread().getName()?+?"\t??"?+?threadLocal.get());
????????????},?String.valueOf(i)).start();
????????}
????}
}
通過(guò)引入 ThreadLocal 后,查看運(yùn)行結(jié)果如下:
-----------------------------------------
-----------------------------------------
4??????4的數(shù)據(jù)
-----------------------------------------
3??????3的數(shù)據(jù)
-----------------------------------------
2??????2的數(shù)據(jù)
-----------------------------------------
1??????1的數(shù)據(jù)
0??????0的數(shù)據(jù)
發(fā)現(xiàn)不會(huì)出現(xiàn)上面的情況了,也就是當(dāng)前線程只能獲取線程線程存儲(chǔ)的對(duì)象
ThreadLocal類和Synchronized關(guān)鍵字
Synchronized同步方式
對(duì)于上述的例子,完全可以通過(guò)加鎖的方式來(lái)實(shí)現(xiàn)這個(gè)功能,我們來(lái)看一下用 Synchronized 代碼塊實(shí)現(xiàn)的效果:
????public?static?void?main(String[]?args)?{
????????MyDemo03?myDemo01?=?new?MyDemo03();
????????for?(int?i?=?0;?i?5;?i++)?{
????????????new?Thread(()?->?{
????????????????synchronized?(MyDemo03.class)?{
????????????????????myDemo01.setContent(Thread.currentThread().getName()?+?"的數(shù)據(jù)");
????????????????????System.out.println("-----------------------------------------");
????????????????????System.out.println(Thread.currentThread().getName()?+?"\t??"?+?myDemo01.getContent());
????????????????}
????????????},?String.valueOf(i)).start();
????????}
????}
運(yùn)行結(jié)果如下所示,發(fā)現(xiàn)通過(guò)加鎖可以實(shí)現(xiàn)與ThreadLocal線程隔離的功能,但是并發(fā)性降低了。
-----------------------------------------
0??????0的數(shù)據(jù)
-----------------------------------------
4??????4的數(shù)據(jù)
-----------------------------------------
3??????3的數(shù)據(jù)
-----------------------------------------
2??????2的數(shù)據(jù)
-----------------------------------------
1??????1的數(shù)據(jù)
ThreadLocal與Synchronized的區(qū)別
雖然 ThreadLocal 模式與 Synchronized 關(guān)鍵字都用于處理多線程并發(fā)訪問(wèn)變量的問(wèn)題,不過(guò)兩者處理問(wèn)題的角度和思路不同。
| Synchronized | ThreadLocal | |
|---|---|---|
| 原理 | 同步機(jī)制采用以時(shí)間換空間的方式,只提供了一份變量,讓不同的線程排隊(duì)訪問(wèn) | ThreadLocal采用以空間換時(shí)間的概念,為每個(gè)線程都提供一份變量副本,從而實(shí)現(xiàn)同時(shí)訪問(wèn)而互不干擾 |
| 側(cè)重點(diǎn) | 多個(gè)線程之間訪問(wèn)資源的同步 | 多線程中讓每個(gè)線程之間的數(shù)據(jù)相互隔離 |
總結(jié):在剛剛的案例中,雖然使用 ThreadLocal 和 Synchronized 都能解決問(wèn)題,但是使用 ThreadLocal 更為合適,因?yàn)檫@樣可以使程序擁有更高的并發(fā)性。
運(yùn)用場(chǎng)景
通過(guò)以上的介紹,我們已經(jīng)基本了解 ThreadLocal 的特點(diǎn),但是它具體是運(yùn)用在什么場(chǎng)景中的呢?接下來(lái)讓我們看一個(gè)案例:事務(wù)操作
轉(zhuǎn)賬案例
這里們先構(gòu)建一個(gè)簡(jiǎn)單的轉(zhuǎn)賬場(chǎng)景:有一個(gè)數(shù)據(jù)表 account ,里面有兩個(gè)用戶 jack 和 Rose,用戶 Jack 給用戶Rose 轉(zhuǎn)賬。案例的實(shí)現(xiàn)主要是用 mysql 數(shù)據(jù)庫(kù),JDBC 和 C3P0 框架,以下是詳細(xì)代碼
這里們先構(gòu)建一個(gè)簡(jiǎn)單的轉(zhuǎn)賬場(chǎng)景:有一個(gè)數(shù)據(jù)表 account ,里面有兩個(gè)用戶 jack 和 Rose,用戶 Jack 給用戶Rose 轉(zhuǎn)賬。案例的實(shí)現(xiàn)主要是用 mysql 數(shù)據(jù)庫(kù),JDBC 和 C3P0 框架,以下是詳細(xì)代碼

引入事務(wù)
案例中轉(zhuǎn)賬涉及兩個(gè) DML 操作:一個(gè)轉(zhuǎn)出,一個(gè)轉(zhuǎn)入。這些操作是需要具備原子性的,不可分割。不然有可能出現(xiàn)數(shù)據(jù)修改異常情況。
public?class?AccountService?{
????public?boolean?transfer(String?outUser,?String?isUser,?int?money)?{
????????AccountDao?ad?=?new?AccountDao();
????????try?{
????????????//?轉(zhuǎn)出
????????????ad.out(outUser,?money);
????????????//?模擬轉(zhuǎn)賬過(guò)程中的異常
????????????int?i?=?1/0;
????????????//?轉(zhuǎn)入
????????????ad.in(inUser,?money);
????????}?catch(Exception?e)?{
????????????e.printStackTrace();
????????????return?false;
????????}
????????return?true;
????}
}
所以這里就需要操作事務(wù),來(lái)保證轉(zhuǎn)入和轉(zhuǎn)出具備原子性,要么成功,要么失敗。
JDBC 中關(guān)于事務(wù)操作的 API
| Connection接口的方法 | 作用 |
|---|---|
| void setAutoCommit(false) | 禁用事務(wù)自動(dòng)提交(改為手動(dòng)提交) |
| void commit() | 提交事務(wù) |
| void rollbakc() | 回滾事務(wù) |
開啟事務(wù)的注意點(diǎn)
為了保證所有操作在一個(gè)事務(wù)中,案例中使用的連接必須是同一個(gè);
service 層開啟事務(wù)的 connection 需要跟 dao 層訪問(wèn)數(shù)據(jù)庫(kù)的 connection 保持一致
線程并發(fā)情況下,每個(gè)線程只能操作各自的 connection,也就是線程隔離
常規(guī)解決方法
基于上面給出的前提,大家通常想到的解決方法
從 service 層將 connection 對(duì)象向 dao 層傳遞
加鎖
常規(guī)解決方法的弊端
提高代碼的耦合度(因?yàn)槲覀冃枰獜?service 層 傳入 connection 參數(shù))
降低程序的性能(加了同步代碼塊,失去了并發(fā)性)
這個(gè)時(shí)候就可以通過(guò) ThreadLocal 和當(dāng)前線程進(jìn)行綁定,來(lái)降低代碼之間的耦合

使用ThreadLocal解決
針對(duì)上面出現(xiàn)的情況,我們需要對(duì)原來(lái)的JDBC連接池對(duì)象進(jìn)行更改
將原來(lái)從連接池中獲取對(duì)象,改成直接獲取當(dāng)前線程綁定的連接對(duì)象
如果連接對(duì)象是空的
再去連接池中獲取連接
將此連接對(duì)象跟當(dāng)前線程進(jìn)行綁定
ThreadLocal?tl?=?new?ThreadLocal();
public?static?Connection?getConnection()?{
????Connection?conn?=?tl.get();
????if(conn?==?null)?{
????????conn?=?ds.getConnection();
????????tl.set(conn);
????}
????return?conn;
}
ThreadLocal實(shí)現(xiàn)的好處
從上述的案例中我們可以看到,在一些特定場(chǎng)景下,ThreadLocal方案有兩個(gè)突出的優(yōu)勢(shì):
傳遞數(shù)據(jù):保存每個(gè)線程綁定的數(shù)據(jù),在需要的地方可以直接獲取,避免參數(shù)直接傳遞帶來(lái)的代碼耦合問(wèn)題
線程隔離:各線程之間的數(shù)據(jù)相互隔離卻又具備并發(fā)性,避免同步方式帶來(lái)的性能損失
ThreadLocal的內(nèi)部結(jié)構(gòu)
通過(guò)以上的學(xué)習(xí),我們對(duì) ThreadLocal 的作用有了一定的認(rèn)識(shí)。現(xiàn)在我們一起來(lái)看一下 ThreadLocal 的內(nèi)部結(jié)構(gòu),探究它能夠?qū)崿F(xiàn)線程數(shù)據(jù)隔離的原理。
常見誤解
如果我們不去看源代碼的話,可能會(huì)猜測(cè) ThreadLocal 是這樣子設(shè)計(jì)的:每個(gè) ThreadLocal 都創(chuàng)建一個(gè) Map,然后用線程作為 Map 的 key,要存儲(chǔ)的局部變量作為 Map 的 value,這樣就能達(dá)到各個(gè)線程的局部變量隔離的效果。這是最簡(jiǎn)單的設(shè)計(jì)方法,JDK最早期的 ThreadLocal 確實(shí)是這樣設(shè)計(jì)的,但現(xiàn)在早已不是了。

現(xiàn)在的設(shè)計(jì)
但是,JDK 后面優(yōu)化了設(shè)計(jì)方案,在 JDK8 中 ThreadLocal 的設(shè)計(jì)是:每個(gè) Thread 維護(hù)一個(gè)ThreadLocalMap,這個(gè) Map 的 key 是 ThreadLocal 實(shí)例本身,value 才是真正要存儲(chǔ)的值 object。具體的過(guò)程是這樣的:
每個(gè) Thread 線程內(nèi)部都有一個(gè) Map(ThreadLocalMap)
Map 里面存儲(chǔ) ThreadLocal 對(duì)象key 和線程的變量副本 value
Thread 內(nèi)部的 Map 是由 ThreadLocal 維護(hù)的,由 ThreadLocal 負(fù)責(zé)向 map 獲取和設(shè)置線程的變量值。
對(duì)于不同的線程,每次獲取副本值時(shí),別的線程并不能獲取到當(dāng)前線程的副本值,形成了副本的隔離,互不干擾。

從上面變成 JDK8 的設(shè)計(jì)有什么好處?
每個(gè) Map 存儲(chǔ)的 Entry 數(shù)量變少,因?yàn)樵瓉?lái)的 Entry 數(shù)量是由 Thread 決定,而現(xiàn)在是由 ThreadLocal 決定的。真實(shí)開發(fā)中,Thread 的數(shù)量遠(yuǎn)遠(yuǎn)大于 ThreadLocal 的數(shù)量
當(dāng) Thread 銷毀的時(shí)候,ThreadLocalMap 也會(huì)隨之銷毀,因?yàn)?ThreadLocal 是存放在 Thread 中的,隨著 Thread 銷毀而消失,能降低開銷。
ThreadLocalMap源碼分析
在分析 ThreadLocal 方法的時(shí)候,我們了解到 ThreadLocal 的操作實(shí)際上是圍繞 ThreadLocalMap 展開的。ThreadLocalMap 的源碼相對(duì)比較復(fù)雜,我們從以下三個(gè)方面進(jìn)行討論。
基本結(jié)構(gòu)
ThreadLocalMap 是 ThreadLocal 的內(nèi)部類,沒有實(shí)現(xiàn) Map 接口,用獨(dú)立的方式實(shí)現(xiàn)了 Map 的功能,其內(nèi)部的 Entry 也是獨(dú)立實(shí)現(xiàn)。

成員變量
/**
*?初始容量?-?必須是2的整次冪
**/
private?static?final?int?INITIAL_CAPACITY?=?16;
/**
*存放數(shù)據(jù)的table?,Entry類的定義在下面分析,同樣,數(shù)組的長(zhǎng)度必須是2的整次冪
**/
private?Entry[]?table;
/**
*數(shù)組里面entrys的個(gè)數(shù),可以用于判斷table當(dāng)前使用量是否超過(guò)閾值
**/
private?int?size?=?0;
/**
*進(jìn)行擴(kuò)容的閾值,表使用量大于它的時(shí)候進(jìn)行擴(kuò)容
**/
private?int?threshold;?//?Default?to?0
跟 HashMap 類似,INITIAL_CAPACITY 代表這個(gè) Map 的初始容量;table 是一個(gè) Entry 類型的數(shù)組,用于存儲(chǔ)數(shù)據(jù);size 代表表中的存儲(chǔ)數(shù)目;threshold 代表需要擴(kuò)容時(shí)對(duì)應(yīng)的 size 的閾值。
存儲(chǔ)結(jié)構(gòu) - Entry
/*
*Entry繼承WeakRefefence,并且用ThreadLocal作為key.
如果key為nu11(entry.get()==nu11),意味著key不再被引用,
*因此這時(shí)候entry也可以從table中清除。
*/
static?class?Entry?extends?weakReference<ThreadLocal>>{
object value;Entry(ThreadLocal<?>k,object v){
????super(k);
????value?=?v;
}}
在 ThreadLocalMap 中,也是用 Entry 來(lái)保存 K-V 結(jié)構(gòu)數(shù)據(jù)的。不過(guò) Entry 中的 key 只能是 ThreadLocal 對(duì)象,這點(diǎn)在構(gòu)造方法中已經(jīng)限定死了。
另外,Entry 繼承 WeakReference,也就是 **key(ThreadLocal)**是弱引用,其目的是將 ThreadLocal 對(duì)象的生命周期和線程生命周期解綁。
弱引用和內(nèi)存泄漏
有些程序員在使用 ThreadLocal 的過(guò)程中會(huì)發(fā)現(xiàn)有內(nèi)存泄漏的情況發(fā)生,就猜測(cè)這個(gè)內(nèi)存泄漏跟Entry中使用了弱引用的 key 有關(guān)系。這個(gè)理解其實(shí)是不對(duì)的。
我們先來(lái)回顧這個(gè)問(wèn)題中涉及的幾個(gè)名詞概念,再來(lái)分析問(wèn)題。
內(nèi)存泄漏相關(guān)概念
Memory overflow:內(nèi)存溢出,沒有足夠的內(nèi)存提供申請(qǐng)者使用。
Memory leak:內(nèi)存泄漏是指程序中己動(dòng)態(tài)分配的堆內(nèi)存由于某種原因程序未釋放或無(wú)法釋放,造成系統(tǒng)內(nèi)存的浪費(fèi),導(dǎo)致程序運(yùn)行速度減慢甚至系統(tǒng)潰等嚴(yán)重后果。I內(nèi)存泄漏的堆積終將導(dǎo)致內(nèi)存溢出。
弱引用相關(guān)概念
Java中的引用有4種類型:強(qiáng)、軟、弱、虛。當(dāng)前這個(gè)問(wèn)題主要涉及到強(qiáng)引用和弱引用:
強(qiáng)引用:就是我們最常見的普通對(duì)象引用,只要還有強(qiáng)引用指向一個(gè)對(duì)象,就能表明對(duì)象還“活著”,垃圾回收器就不會(huì)回收這種對(duì)象。
弱引用:垃圾回收器一旦發(fā)現(xiàn)了只具有弱引用的對(duì)象,不管當(dāng)前內(nèi)存空間足夠與否,都會(huì)回收它的內(nèi)存。
如果key使用強(qiáng)引用,那么會(huì)出現(xiàn)內(nèi)存泄漏?
假設(shè) ThreadLocalMap 中的 key 使用了強(qiáng)引用,那么會(huì)出現(xiàn)內(nèi)存泄漏嗎?
此時(shí) ThreadLocal 的內(nèi)存圖(實(shí)線表示強(qiáng)引用)如下:

假設(shè)在業(yè)務(wù)代碼中使用完 ThreadLocal,threadLocal Ref被回收了
但是因?yàn)?threadLocalMap 的 Entry 強(qiáng)引用了 threadLocal,造成 threadLocal 無(wú)法被回收。
在沒有手動(dòng)刪除這個(gè) Entry 以及 CurrentThread 依然運(yùn)行的前提下,始終有強(qiáng)引用鏈 threadRef->currentThread->threadLocalMap->entry,Entry 就不會(huì)被回收( Entry 中包括了ThreadLocal實(shí)例和value),導(dǎo)致Entry內(nèi)存泄漏。
也就是說(shuō),ThreadLocalMap 中的 key 使用了強(qiáng)引用,是無(wú)法完全避免內(nèi)存泄漏的。
如果key使用弱引用,那么會(huì)出現(xiàn)內(nèi)存泄漏?

同樣假設(shè)在業(yè)務(wù)代碼中使用完 ThreadLocal ,threadLocal Ref 被回收了。
由于 ThreadLocalMap 只持有 ThreadLocal 的弱引用,沒有任何強(qiáng)引用指向 threadlocal 實(shí)例,所以threadlocal 就可以順利被 gc 回收,此時(shí) Entry 中的 key=null 。
但是在沒有手動(dòng)刪除這個(gè) Entry 以及 CurrentThread 依然運(yùn)行的前提下,也存在有強(qiáng)引用鏈 threadRef->currentThread->threadLocalMap->entry-> value,value 不會(huì)被回收,而這塊 value 永遠(yuǎn)不會(huì)被訪問(wèn)到了,導(dǎo)致 value 內(nèi)存泄漏。
也就是說(shuō),ThreadLocalMap 中的 key 使用了弱引用,也有可能內(nèi)存泄漏。
出現(xiàn)內(nèi)存泄漏的真實(shí)原因
比較以上兩種情況,我們就會(huì)發(fā)現(xiàn),內(nèi)存泄漏的發(fā)生跟 ThreadLocalMap 中的 key 是否使用弱引用是沒有關(guān)系的。那么內(nèi)存泄漏的的真正原因是什么呢?
細(xì)心的同學(xué)會(huì)發(fā)現(xiàn),在以上兩種內(nèi)存泄漏的情況中,都有兩個(gè)前提:
沒有手動(dòng)刪除這個(gè) Entry
CurrentThread 依然運(yùn)行
第一點(diǎn)很好理解,只要在使用完 ThreadLocal,調(diào)用其 remove 方法刪除對(duì)應(yīng)的 Entry,就能避免內(nèi)存泄漏。
第二點(diǎn)稍微復(fù)雜一點(diǎn),由于 ThreadLocalMap 是 Thread 的一個(gè)屬性,被當(dāng)前線程所引用,所以它的生命周期跟 Thread 一樣長(zhǎng)。那么在使用完 ThreadLocal 的使用,如果當(dāng)前 Thread 也隨之執(zhí)行結(jié)束,ThreadLocalMap 自然也會(huì)被 gc 回收,從根源上避免了內(nèi)存泄漏。
綜上,ThreadLocal 內(nèi)存泄漏的根源是:由于 ThreadLocalMap 的生命周期跟 Thread 一樣長(zhǎng),如果沒有手動(dòng)刪除對(duì)應(yīng) key 就會(huì)導(dǎo)致內(nèi)存泄漏。
為什么要使用弱引用?
根據(jù)剛才的分析,我們知道了:無(wú)論 ThreadLocalMap 中的 key 使用哪種類型引用都無(wú)法完全避免內(nèi)存泄漏,跟使用弱引用沒有關(guān)系。
要避免內(nèi)存泄漏有兩種方式:
使用完 ThreadLocal,調(diào)用其 remove 方法刪除對(duì)應(yīng)的 Entry
使用完 ThreadLocal,當(dāng)前 Thread 也隨之運(yùn)行結(jié)束
相對(duì)第一種方式,第二種方式顯然更不好控制,特別是使用線程池的時(shí)候,線程結(jié)束是不會(huì)銷毀的,而是接著放入了線程池中。
也就是說(shuō),只要記得在使用完 ThreadLocal 及時(shí)的調(diào)用 remove,無(wú)論 key 是強(qiáng)引用還是弱引用都不會(huì)有問(wèn)題。那么為什么 key 要用弱引用呢?
事實(shí)上,在 ThreadLocalMap 中的 set / getEntry 方法中,會(huì)對(duì) key 為 null(也即是 ThreadLocal 為null)進(jìn)行判斷,如果為 null 的話,那么是會(huì)對(duì) value 置為 null 的。
這就意味著使用完 ThreadLocal,CurrentThread 依然運(yùn)行的前提下,就算忘記調(diào)用 remove 方法,弱引用比強(qiáng)引用可以多一層保障:弱引用 的ThreadLocal 會(huì)被回收,對(duì)應(yīng)的 value 在下一次 ThreadLocalMap 調(diào)用set,get,remove 中的任一方法的時(shí)候會(huì)被清除,從而避免內(nèi)存泄漏。
