<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          面試官:談?wù)勀銓?duì)ThreadLocal的理解?

          共 7608字,需瀏覽 16分鐘

           ·

          2021-11-03 19:22

          大家好,我是陌溪

          ThreadLocal 作為 Java 面試的高頻題,陌溪在之前面試的時(shí)候也遇到過(guò),所以后面專門針對(duì) ThreadLocal 寫了一份筆記,讓我們一起來(lái)看看~

          什么是ThreadLocal?

          Java 官方文檔中的描述:ThreadLocal 類用來(lái)提供線程內(nèi)部的局部變量。這種變量在多線程環(huán)境下訪問(wèn)(通過(guò)getset 方法訪問(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)題的角度和思路不同。


          SynchronizedThreadLocal
          原理同步機(jī)制采用以時(shí)間換空間的方式,只提供了一份變量,讓不同的線程排隊(duì)訪問(wèn)ThreadLocal采用以空間換時(shí)間的概念,為每個(gè)線程都提供一份變量副本,從而實(shí)現(xiàn)同時(shí)訪問(wèn)而互不干擾
          側(cè)重點(diǎn)多個(gè)線程之間訪問(wèn)資源的同步多線程中讓每個(gè)線程之間的數(shù)據(jù)相互隔離

          總結(jié):在剛剛的案例中,雖然使用 ThreadLocalSynchronized 都能解決問(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è)用戶 jackRose,用戶 Jack 給用戶Rose 轉(zhuǎn)賬。案例的實(shí)現(xiàn)主要是用 mysql 數(shù)據(jù)庫(kù),JDBCC3P0 框架,以下是詳細(xì)代碼

          這里們先構(gòu)建一個(gè)簡(jiǎn)單的轉(zhuǎn)賬場(chǎng)景:有一個(gè)數(shù)據(jù)表 account ,里面有兩個(gè)用戶 jackRose,用戶 Jack 給用戶Rose 轉(zhuǎn)賬。案例的實(shí)現(xiàn)主要是用 mysql 數(shù)據(jù)庫(kù),JDBCC3P0 框架,以下是詳細(xì)代碼

          image-20200710204941153

          引入事務(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,然后用線程作為 Mapkey,要存儲(chǔ)的局部變量作為 Mapvalue,這樣就能達(dá)到各個(gè)線程的局部變量隔離的效果。這是最簡(jiǎn)單的設(shè)計(jì)方法,JDK最早期的 ThreadLocal 確實(shí)是這樣設(shè)計(jì)的,但現(xiàn)在早已不是了。

          ThreadLocal早期內(nèi)部結(jié)構(gòu)

          現(xiàn)在的設(shè)計(jì)

          但是,JDK 后面優(yōu)化了設(shè)計(jì)方案,在 JDK8ThreadLocal 的設(shè)計(jì)是:每個(gè) Thread 維護(hù)一個(gè)ThreadLocalMap,這個(gè) MapkeyThreadLocal 實(shí)例本身,value 才是真正要存儲(chǔ)的值 object。具體的過(guò)程是這樣的:

          • 每個(gè) Thread 線程內(nèi)部都有一個(gè) MapThreadLocalMap

          • Map 里面存儲(chǔ) ThreadLocal 對(duì)象key 和線程的變量副本 value

          • Thread 內(nèi)部的 Map 是由 ThreadLocal 維護(hù)的,由 ThreadLocal 負(fù)責(zé)向 map 獲取和設(shè)置線程的變量值。

          • 對(duì)于不同的線程,每次獲取副本值時(shí),別的線程并不能獲取到當(dāng)前線程的副本值,形成了副本的隔離,互不干擾。

          JDK8前后的設(shè)計(jì)

          從上面變成 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)

          ThreadLocalMapThreadLocal 的內(nèi)部類,沒有實(shí)現(xiàn) Map 接口,用獨(dú)立的方式實(shí)現(xiàn)了 Map 的功能,其內(nèi)部的 Entry 也是獨(dú)立實(shí)現(xiàn)。

          基本結(jié)構(gòu)

          成員變量

          /**
          *?初始容量?-?必須是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)引用)如下:

          ThreadLocal內(nèi)存圖
          • 假設(shè)在業(yè)務(wù)代碼中使用完 ThreadLocalthreadLocal Ref被回收了

          • 但是因?yàn)?threadLocalMapEntry 強(qiáng)引用了 threadLocal,造成 threadLocal 無(wú)法被回收。

          • 在沒有手動(dòng)刪除這個(gè) Entry 以及 CurrentThread 依然運(yùn)行的前提下,始終有強(qiáng)引用鏈 threadRef->currentThread->threadLocalMap->entryEntry 就不會(huì)被回收( Entry 中包括了ThreadLocal實(shí)例和value),導(dǎo)致Entry內(nèi)存泄漏。

          也就是說(shuō),ThreadLocalMap 中的 key 使用了強(qiáng)引用,是無(wú)法完全避免內(nèi)存泄漏的。

          如果key使用弱引用,那么會(huì)出現(xiàn)內(nèi)存泄漏?

          ThreadLocal內(nèi)存圖
          • 同樣假設(shè)在業(yè)務(wù)代碼中使用完 ThreadLocalthreadLocal 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-> valuevalue 不會(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),由于 ThreadLocalMapThread 的一個(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ì) keynull(也即是 ThreadLocalnull)進(jìn)行判斷,如果為 null 的話,那么是會(huì)對(duì) value 置為 null 的。

          這就意味著使用完 ThreadLocalCurrentThread 依然運(yùn)行的前提下,就算忘記調(diào)用 remove 方法,弱引用比強(qiáng)引用可以多一層保障:弱引用 的ThreadLocal 會(huì)被回收,對(duì)應(yīng)的 value 在下一次 ThreadLocalMap 調(diào)用set,get,remove 中的任一方法的時(shí)候會(huì)被清除,從而避免內(nèi)存泄漏。

          瀏覽 67
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  WWW.精品天堂网一区二区三区 | 91精品无码 | 黄色色情网站免费观看 | 久久精品99久久久久久 | 小黄片免费视频 |