<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>

          面試被問五種線程安全類型,怎么破?

          共 1563字,需瀏覽 4分鐘

           ·

          2021-11-14 00:20

          你知道的越多,不知道的就越多,業(yè)余的像一棵小草!

          你來,我們一起精進!你不來,我和你的競爭對手一起精進!

          編輯:業(yè)余草

          blog.csdn.net/u014454538/article/details/98515807

          推薦:https://www.xttblog.com/?p=5290

          1. Java中的線程安全

          • Java線程安全:狹義地認為是多線程之間共享數(shù)據(jù)的訪問。

          • Java語言中各種操作共享的數(shù)據(jù)有5種類型:不可變、絕對線程安全、相對線程安全、線程兼容、線程獨立

          ① 不可變

          • 不可變(Immutable) 的對象一定是線程安全的,不需要再采取任何的線程安全保障措施。

          • 只要能正確構(gòu)建一個不可變對象,該對象永遠不會在多個線程之間出現(xiàn)不一致的狀態(tài)。

          • 多線程環(huán)境下,應(yīng)當盡量使對象成為不可變,來滿足線程安全。

          「如何實現(xiàn)不可變?」

          • 如果共享數(shù)據(jù)是基本數(shù)據(jù)類型,使用final關(guān)鍵字對其進行修飾,就可以保證它是不可變的。

          • 如果共享數(shù)據(jù)是一個對象,要保證對象的行為不會對其狀態(tài)產(chǎn)生任何影響。

          • String是不可變的,對其進行substring()、replace()、concat()等操作,返回的是新的String對象,原始的String對象的值不受影響。而如果對StringBuffer或者StringBuilder對象進行substring()、replace()、append()等操作,直接對原對象的值進行改變。

          • 要構(gòu)建不可變對象,需要將內(nèi)部狀態(tài)變量定義為final類型。如java.lang.Integer類中將value定義為final類型。

          private?final?int?value;

          「常見的不可變的類型:」

          • final關(guān)鍵字修飾的基本數(shù)據(jù)類型

          • 枚舉類型、String類型

          • 常見的包裝類型:Short、Integer、Long、Float、Double、Byte、Character等

          • 大數(shù)據(jù)類型:BigInteger、BigDecimal

          ?

          注意:原子類 AtomicInteger 和 AtomicLong 則是可變的。

          ?

          對于集合類型,可以使用 Collections.unmodifiableXXX() 方法來獲取一個不可變的集合。

          • 通過Collections.unmodifiableMap(map)獲的一個不可變的Map類型。

          • Collections.unmodifiableXXX() 先對原始的集合進行拷貝,需要對集合進行修改的方法都直接拋出異常。

          例如,如果獲得的不可變map對象進行put()、remove()、clear()操作,則會拋出UnsupportedOperationException異常。

          ② 絕對線程安全

          絕對線程安全的實現(xiàn),通常需要付出很大的、甚至不切實際的代價。

          Java API中提供的線程安全,大多數(shù)都不是絕對線程安全。

          例如,對于數(shù)組集合Vector的操作,如get()、add()、remove()都是有synchronized關(guān)鍵字修飾。有時調(diào)用時也需要手動添加同步手段,保證多線程的安全。

          下面的代碼看似不需要同步,實際運行過程中會報錯。

          import?java.util.Vector;

          public?class?VectorTest?{
          ????public?static?void?main(String[]?args)?{
          ????????Vector?vector?=?new?Vector<>();
          ????????while(true){
          ????????????for?(int?i?=?0;?i?10;?i++)?{
          ????????????????vector.add(i);
          ????????????}
          ????????????new?Thread(new?Runnable()?{
          ????????????????@Override
          ????????????????public?void?run()?{
          ????????????????????for?(int?i?=?0;?i?????????????????????????System.out.println("獲取vector的第"?+?i?+?"個元素:?"?+?vector.get(i));
          ????????????????????}
          ????????????????}
          ????????????}).start();
          ????????????new?Thread(new?Runnable()?{
          ????????????????@Override
          ????????????????public?void?run()?{
          ????????????????????for?(int?i=0;i????????????????????????System.out.println("刪除vector中的第"?+?i+"個元素");
          ????????????????????????vector.remove(i);
          ????????????????????}
          ????????????????}
          ????????????}).start();
          ????????????while?(Thread.activeCount()>20)
          ????????????????return;
          ????????}
          ????}
          }

          出現(xiàn)ArrayIndexOutOfBoundsException異常,原因:某個線程恰好刪除了元素i,使得當前線程無法訪問元素i。

          Exception?in?thread?"Thread-1109"?java.lang.ArrayIndexOutOfBoundsException:?Array?index?out?of?range:?1
          ?at?java.util.Vector.remove(Vector.java:831)
          ?at?VectorTest$2.run(VectorTest.java:28)
          ?at?java.lang.Thread.run(Thread.java:745)

          需要將對元素的get和remove構(gòu)造成同步代碼塊:

          synchronized?(vector){
          ????for?(int?i?=?0;?i?????????System.out.println("獲取vector的第"?+?i?+?"個元素:?"?+?vector.get(i));
          ????}
          }
          synchronized?(vector){
          ????for?(int?i=0;i????????System.out.println("刪除vector中的第"?+?i+"個元素");
          ????????vector.remove(i);
          ????}
          }

          ③ 相對線程安全

          • 相對線程安全需要保證對該對象的單個操作是線程安全的,在必要的時候可以使用同步措施實現(xiàn)線程安全。

          • 大部分的線程安全類都屬于相對線程安全,如Java容器中的Vector、HashTable、通過Collections.synchronizedXXX()方法包裝的集合。

          ④ 線程兼容

          • Java中大部分的類都是線程兼容的,通過添加同步措施,可以保證在多線程環(huán)境中安全使用這些類的對象。

          • 如常見的ArrayList、HashTableMap都是線程兼容的。

          ⑤ 線程對立

          • 線程對立是指:無法通過添加同步措施,實現(xiàn)多線程中的安全使用。

          • 線程對立的常見操作有:Thread類的suspend()和resume()(已經(jīng)被JDK聲明廢除),System.setIn()System.setOut()等。

          2. Java的枚舉類型

          通過enum關(guān)鍵字修飾的數(shù)據(jù)類型,叫枚舉類型。

          • 枚舉類型的每個元素都有自己的序號,通常從0開始編號。

          • 可以通過values()方法遍歷枚舉類型,通過name()或者toString()獲取枚舉類型的名稱

          • 通過ordinal()方法獲取枚舉類型中元素的序號

          public?class?EnumData?{
          ????public?static?void?main(String[]?args)?{
          ????????for?(Family?family?:?Family.values())?{
          ????????????System.out.println(family.name()?+?":"?+?family.ordinal());
          ????????}
          ????}
          }

          enum?Family?{
          ????GRADMOTHER,?GRANDFATHER,?MOTHER,?FATHER,?DAUGHTER,?SON;
          }

          可以將枚舉類型看做普通的class,在里面定義final類型的成員變量,便可以為枚舉類型中的元素賦初值。

          要想獲取枚舉類型中元素實際值,需要為成員變量添加getter方法。

          雖然枚舉類型的元素有了自己的實際值,但是通過ordinal()方法獲取的元素序號不會發(fā)生改變。

          public?class?EnumData?{
          ????public?static?void?main(String[]?args)?{
          ????????for?(Family?family?:?Family.values())?{
          ????????????System.out.println(family.name()?+?":實際值"?+?family.getValue()?+
          ????????????????????",?實際序號"?+?family.ordinal());
          ????????}
          ????}
          }
          enum?Family?{
          ????GRADMOTHER(3),?GRANDFATHER(4),?MOTHER(1),?FATHER(2),?DAUGHTER(5),?SON(6);
          ????private?final?int?value;
          ????Family(int?value)?{
          ????????this.value?=?value;
          ????}
          ????public?int?getValue()?{
          ????????return?value;
          ????}
          }

          3. Java線程安全的實現(xiàn)

          ① 互斥同步

          互斥同步(Mutex Exclusion & Synchronization)是一種常見的并發(fā)正確性保障手段。

          • 同步:多個線程并發(fā)訪問共享數(shù)據(jù),保證共享數(shù)據(jù)同一時刻只被一個(或者一些,使用信號量)線程使用。

          • 互斥:互斥是實現(xiàn)同步的一種手段,主要的互斥實現(xiàn)方式:臨界區(qū)(Critical Section)、互斥量(Mutex)、信號量(Semaphore)。

          「同步與互斥的關(guān)系:」

          • 互斥是原因,同步是結(jié)果。

          • 同步是目的,互斥是方法。

          Java中,最基本的實現(xiàn)互斥同步的手段是synchronized關(guān)鍵字,其次是JUC包中的ReentrantLock。

          「關(guān)于synchronized關(guān)鍵字:」

          • 編譯后的同步塊,開始處會添加monitorenter指令,結(jié)束處或異常處會添加monitorexit指令。

          • monitorenter和monitorexit指令中都包含一個引用類型的參數(shù),分別指向加鎖或解鎖的對象。如果是同步代碼塊,則為synchronized括號中明確指定的對象;如果為普通方法,則為當前實例對象;如果為靜態(tài)方法,則為類對應(yīng)的class對象。

          • JVM執(zhí)行monitorenter指令時,要先嘗試獲取鎖:如果對象沒被鎖定或者當前線程已經(jīng)擁有該對象的鎖,則鎖計數(shù)器加1;否則獲取鎖失敗,進入阻塞狀態(tài),等待持有鎖的線程釋放鎖。

          • JVM執(zhí)行monitorexit指令時,鎖計數(shù)器減1,直到計數(shù)器的值為0,鎖被釋放。(synchronized是支持重進入的)

          • 由于阻塞或者喚醒線程都需要從用戶態(tài)(User Mode)切換到核心態(tài)(Kernel Mode),有時鎖只會被持有很短的時間,沒有必要進行狀態(tài)轉(zhuǎn)換。可以讓線程在阻塞之前先自旋等待一段時間,超時未獲取到鎖才進入阻塞狀態(tài),這樣可以避免頻繁的切入到核心態(tài)。其實,就是后面自旋鎖的思想。

          「關(guān)于ReentrantLock:」

          • 與synchronized關(guān)鍵字相比,它是API層面的互斥鎖(lock()、unlock()、try...finally)。

          • 與synchronized關(guān)鍵字相比,具有可中斷、支持公平與非公平性、可綁定多個Condition對象的高級功能。

          • 由于synchronized關(guān)鍵字被優(yōu)化,二者的性能差異并不是很大,如果不是想使用ReentrantLock的高級功能,優(yōu)先考慮使用synchronized關(guān)鍵字。

          ② 非阻塞同步

          (1)CAS概述

          互斥同步最大的性能問題是線程的阻塞和喚醒,因此又叫阻塞同步。

          互斥同步采用悲觀并發(fā)策略:

          • 多線程并發(fā)訪問共享數(shù)據(jù)時,總是認為只要不加正確的同步措施,肯定會出現(xiàn)問題。

          • 無論共享數(shù)據(jù)是否存在競爭,都會執(zhí)行加鎖、用戶態(tài)和心態(tài)的切換、維護鎖計數(shù)器、檢查是否有被阻塞的線程需要喚醒等操作。

          隨著硬件指令集的發(fā)展,我們可以采用基于沖突檢測的樂觀并發(fā)策略:

          • 先進行操作,如果不存在沖突(即沒有其他線程爭用共享數(shù)據(jù)),則操作成功。

          • 如果有其他線程爭用共享數(shù)據(jù),產(chǎn)生了沖突,使用其他的補償措施。

          • 常見的補償措施:不斷嘗試,直到成功為止,比如循環(huán)的CAS操作。

          樂觀并發(fā)策略的許多實現(xiàn)都不需要將線程阻塞,這種同步操作叫做非阻塞同步。

          非阻塞同步依靠的硬件指令集:前三條是比較久遠的指令,后兩條是現(xiàn)代處理器新增的。

          • 測試和設(shè)置(Test and Set)

          • 獲取并增加(Fetch and Increment)

          • 交換(Swap)

          • 比較并交換(Compare and Swap,即CAS)

          • 加載鏈接/條件存儲(Load Linked/ Store Conditional,即LL/SC)

          「什么是CAS?」

          • CAS,即Compare and Swap,需要借助處理器的cmpxchg指令完成。

          • CAS指令需要三個操作數(shù):內(nèi)存位置V(Java中可以簡單的理解為變量的內(nèi)存地址)、舊的期待值A(chǔ)、新值B。

          • CAS指令執(zhí)行時,當且僅當V符合舊的預(yù)期值A(chǔ),處理器才用新值B更新V的值;否則,不執(zhí)行更新。

          • 不管是否更新V的值,都返回V的舊值,整個處理過程是一個原子操作。

          原子操作:所謂的原子操作是指一個或一系列不可被中斷的操作。

          「Java中的CAS操作:」

          • Java中的CAS操作由sun.misc.Unsafe中的compareAndSwapInt()、compareAndSwapLong()等幾個方法包裝提供。實際無法調(diào)用這些方法,需要采用反射機制才能使用。

          • 在實際的開發(fā)過程中,一般通過其他的Java API調(diào)用它們,如JUC包原子類中的compareAndSet(expect, update) 、getAndIncrement()等方法。這些方法內(nèi)部都使用了Unsafe類的CAS操作。

          • Unsafe類的CAS操作,通過JVM的即時編譯器編譯后,是一條與平臺相關(guān)的CAS指令。

          除了偏向鎖,Java中其他鎖的實現(xiàn)方式都是用了循環(huán)的CAS操作。

          (2)通過循環(huán)的CAS實現(xiàn)原子操作

          通過++i或者i++可以實現(xiàn)計數(shù)器的自增,在多線程環(huán)境下,這樣使用是非線程安全的。

          public?class?UnsafeCount?{
          ????private?int?i?=?0;
          ????private?static?final?int?THREADS_COUNT?=?200;

          ????public?static?void?main(String[]?args)?{
          ????????Thread[]?threads?=?new?Thread[THREADS_COUNT];
          ????????UnsafeCount?counter?=?new?UnsafeCount();
          ????????for?(int?i?=?0;?i?????????????threads[i]?=?new?Thread(new?Runnable()?{
          ????????????????@Override
          ????????????????public?void?run()?{
          ????????????????????for?(int?j?=?0;?j?10000;?j++)?{
          ????????????????????????counter.count();
          ????????????????????}
          ????????????????}
          ????????????});
          ????????????threads[i].start();
          ????????}
          ????????while?(Thread.activeCount()?>?1)?{
          ????????????Thread.yield();
          ????????}
          ????????System.out.println("多線程調(diào)用計數(shù)器i,運行后的值為:?"?+?counter.i);
          ????}

          ????public?void?count()?{
          ????????i++;
          ????}
          }

          運行以上的代碼發(fā)現(xiàn):當線程數(shù)量增加,每個線程調(diào)用計數(shù)器的次數(shù)變大時,每次運行的結(jié)果是錯誤且不固定的。

          為了實現(xiàn)實在一個多線程環(huán)境下、線程安全的計數(shù)器,需要使用AtomicInteger的原子自增運算。

          import?java.util.concurrent.atomic.AtomicInteger;
          public?class?SafeCount?{
          ????private?AtomicInteger?atomic?=?new?AtomicInteger(0);
          ????private?static?final?int?THREAD_COUNT?=?200;
          ????public?static?void?main(String[]?args)?{
          ????????SafeCount?counter?=?new?SafeCount();
          ????????Thread[]?threads?=?new?Thread[THREAD_COUNT];
          ????????for?(int?i?=?0;?i?????????????threads[i]?=?new?Thread(new?Runnable()?{
          ????????????????@Override
          ????????????????public?void?run()?{
          ????????????????????for?(int?j=0;j<10000;j++){
          ????????????????????????counter.count();
          ????????????????????}
          ????????????????}
          ????????????});
          ????????????threads[i].start();
          ????????}
          ????????while?(Thread.activeCount()>1){
          ????????????Thread.yield();
          ????????}
          ????????System.out.println("多線程調(diào)用線程安全的計數(shù)器atomic:"+counter.atomic);
          ????}
          ????public?void?count()?{
          ????????//?調(diào)用compareAnSet方法,使用循環(huán)的CAS操作實現(xiàn)計數(shù)器的原子自增
          ????????for?(;?;?)?{
          ????????????int?expect?=?atomic.get();
          ????????????int?curVal?=?expect?+?1;
          ????????????if?(atomic.compareAndSet(expect,?curVal))?{
          ????????????????break;
          ????????????}
          ????????}
          ????}
          }

          與非線程安全的計數(shù)器相比,線程安全的計數(shù)器有以下特點:

          • 將int類型的計數(shù)器變量i,更換成具有CAS操作的AtomicInteger類型的計數(shù)器變量atomic。

          • 進行自增運算時,通過循環(huán)的CAS操作實現(xiàn)atomic的原子自增。

          • 先通過atomic.get()獲取expect的值,將expect加一得到新值,然后通過atomic.compareAndSet(expect, curVal)這一方法實現(xiàn)CAS操作。

          • 其中compareAndSet()返回的true或者false,表示此次CAS操作是否成功。如果返回false,則不停地重復(fù)執(zhí)行CAS操作,直到操作成功。

          上面的count方法實現(xiàn)的AtomicInteger原子自增,可以只需要調(diào)用incrementAndGet()一個方法就能實現(xiàn)。

          public?void?count()?{
          ????//?調(diào)用incrementAndGet方法,實現(xiàn)AtomicInteger的原子自增
          ????atomic.incrementAndGet();
          }

          因為incrementAndGet()方法,封裝了通過循環(huán)的CAS操作實現(xiàn)AtomicInteger原子自增的代碼。

          public?final?int?incrementAndGet()?{
          ????return?unsafe.getAndAddInt(this,?valueOffset,?1)?+?1;
          }
          public?final?int?getAndAddInt(Object?var1,?long?var2,?int?var4)?{
          ????int?var5;
          ????do?{
          ????????var5?=?this.getIntVolatile(var1,?var2);
          ????}?while(!this.compareAndSwapInt(var1,?var2,?var5,?var5?+?var4));
          ????return?var5;
          }
          (3)CAS操作存在的問題

          「1. ABA問題」

          • 在執(zhí)行CAS操作更新共享變量的值時,如果一個值原來是A,被其他線程改成了B,然后又改回成了A。對于該CAS操作來說,它完全感受不到共享變量值的變化。這種操作漏洞稱為CAS操作的ABA問題。

          • 解決該問題的思路是,為變量添加版本號,每次更新時版本號遞增。這種場景下就成了1A --> 2B --> 3A。CAS操作就能檢測到共享變量的ABA問題了。

          • JUC包中,也提供了相應(yīng)的帶標記的原子引用類AtomicStampedReference來解決ABA問題。

          • AtomicStampedReference的compareAndSet()方法會首先比較期待的引用是否等于當前引用,然后檢查期待的標記是否等于當前標記。如果全部相等,則以原子操作的方式將新的引用和新的標記更新到當前值中。

          • 但是AtomicStampedReference目前比較雞肋,如果想解決AB問題,可以使用鎖。

          「2. 循環(huán)時間過長,開銷大」

          循環(huán)的CAS操作如果長時間不成功,會給CPU帶來非常大的執(zhí)行開銷。

          「3. 只能保證一個共享變量的原子操作」

          • 只對一個共享變量執(zhí)行操作時,可以通過循環(huán)的CAS操作實現(xiàn)。如果是多個共享變量,循環(huán)的CAS操作無法保證操作的原子性。

          • 取巧的操作:將多個共享變量合為一個變量進行CAS操作。JDK1.5開始,提供了AtomicReference類保證引用對象之間的原子性,可以將多個變量放在一個對象中進行CAS操作。

          ③ 無同步方案

          同步只是保證共享數(shù)據(jù)爭用時正確性的一種手段,如果不存在共享數(shù)據(jù),自然無須任何同步措施。

          (1)棧封閉

          多個線程訪問同一個方法的局部變量時,不會出現(xiàn)線程安全問題。

          因為方法中的局部變量不會逃出該方法而被其他線程訪問,因此可以看做JVM棧中數(shù)據(jù),屬于線程私有。

          (2)可重入代碼(Reentrant Code)

          可重入代碼又叫純代碼(Pure Code),可在代碼執(zhí)行的任何時候中斷他它,轉(zhuǎn)去執(zhí)行另外一段代碼(包括遞歸調(diào)用它本身),控制權(quán)返回后,原來的程序不會出現(xiàn)任何錯誤。

          所有可重入的代碼都是線程安全,并非所有線程安全的代碼都是可重入的。

          可重入代碼的共同特征:

          • 不依賴存儲在堆上的數(shù)據(jù)和公用的系統(tǒng)資源

          • 用到的狀態(tài)量都由參數(shù)中傳入

          • 不調(diào)用非可重用的方法

          如何判斷代碼是否具備可重入性?如果一個方法,它的返回結(jié)果是可預(yù)測的。只要輸入了相同的數(shù)據(jù),就都能返回相同的結(jié)果,那它就滿足可重入性,當然也就是線程安全的。

          (3)線程本地存儲(TLS)

          線程本地存儲(Thread Local Storage):

          • 如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個線程中執(zhí)行。

          • 如果能保證,我們就可以把共享數(shù)據(jù)的可見范圍限制在同一個線程內(nèi)。

          • 這樣,無須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭用的問題。

          TLS的重要應(yīng)用實例:經(jīng)典的Web交互模型中,一個請求對應(yīng)一個服務(wù)器線程,使得Web服務(wù)器應(yīng)用可以使用。

          Java中沒有關(guān)鍵字可以將一個變量定義為線程所獨享,但是Java中創(chuàng)建了java.lang.ThreadLocal類提供線程本地存儲功能。

          • 每一個線程內(nèi)部都包含一個ThreadLocalMap對象,該對象將ThreadLocal對象的hashCode值作為key,即ThreadLocal.threadLocalHashCode,將本地線程變量作為value,構(gòu)成鍵值對。

          • ThreadLocal對象是當前線程ThreadLocalMap對象的訪問入口,通過threadLocal.set()為本地線程添加獨享變量;通過threadLocal.get()獲取本地線程獨享變量的值。

          • ThreadLocal、ThreadLocalMap、Thread的關(guān)系:Thread對象中包含ThreadLocalMap對象,ThreadLocalMap對象中包含多個鍵值對,每個鍵值對的key是ThreadLocal對象的hashCode,value是本地線程變量。

          ThreadLocal的編程實例:

          • 想為某個線程添加本地線程變量,必須通過ThreadLocal對象在該線程中進行添加,構(gòu)造出的鍵值對自動存入該線程的map中;

          • 想要獲取某個線程的本地線程變量,必須在該線程中獲取,會自動查詢該線程的map,獲得ThreadLocal對象對應(yīng)的value。

          • 通過ThreadLocal對象重復(fù)為某個線程添加鍵值對,會覆蓋之前的value。

          public?class?TLS?{
          ????public?static?void?main(String[]?args)?{
          ????????ThreadLocal?threadLocal1?=?new?ThreadLocal<>();
          ????????ThreadLocal?threadLocal2?=?new?ThreadLocal<>();
          ????????Thread?thread1?=?new?Thread(new?Runnable()?{
          ????????????@Override
          ????????????public?void?run()?{
          ????????????????//?設(shè)置當前線程的本地線程變量
          ????????????????threadLocal1.set("thread1");
          ????????????????threadLocal2.set(1);
          ????????????????System.out.println(threadLocal1.get()?+?":?"?+?threadLocal2.get());
          ????????????????//?使用完畢后要刪除,避免內(nèi)存泄露
          ????????????????threadLocal1.remove();
          ????????????????threadLocal2.remove();
          ????????????}
          ????????});
          ????????Thread?thread2?=?new?Thread(new?Runnable()?{
          ????????????@Override
          ????????????public?void?run()?{
          ????????????????threadLocal1.set("thread2");
          ????????????????threadLocal2.set(2);
          ????????????????System.out.println(threadLocal1.get()?+?":?"?+?threadLocal2.get());
          ????????????????threadLocal1.remove();
          ????????????????threadLocal2.remove();
          ????????????}
          ????????});
          ????????thread1.start();
          ????????thread2.start();
          ????????//?沒有通過ThreadLocal為主線程添加過本地線程變量,獲取到的內(nèi)容都是null
          ????????System.out.println(threadLocal1.get()+":?"+threadLocal2.get());
          ????}
          }

          對ThreadLocal的正確理解:

          • ThreadLocal適用于線程需要有自己的實例變量,該實例變量可以在多個方法中被使用,但是不能被其他線程共享的場景。

          • 由于不存在數(shù)據(jù)共享,何談同步?因此ThreadLocal 從理論上講,不是用來解決多線程并發(fā)問題的。

          「ThreadLocal的實現(xiàn):」

          最原始的想法:ThreadLocal維護線程與實例的映射。既然通過ThreadLocal對象為線程添加本地線程變量,那就將ThreadLocalMap放在ThreadLocal中。

          原始想法存在的缺陷:多線程并發(fā)訪問ThreadLocal中的Map,需要添加鎖。這是, JDK 未采用該方案的一個原因。

          優(yōu)化后的方法:Thread維護ThreadLocal與實例的映射。Map是每個線程所私有,只能在當前線程通過ThreadLocal對象訪問自身的Map。不存在多線程并發(fā)訪問同一個Map的情況,也就不需要鎖。

          優(yōu)化后存在內(nèi)存泄露的情況:JDK1.8中,ThreadLocalMap每個Entry對ThreadLocal對象是弱引用,對每個實例是強引用。當ThreadLocal對象被回收后,該Entry的鍵變成null,但Entry無法被移除。使得實例被Entry引用無法回收,造成內(nèi)存泄露。

          瀏覽 54
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <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>
                  久久亚洲国产视频 | 欧美黄色一级网站 | 天堂中文在线资源视频 | 黄片在线免费观看 | 亚洲日韩欧美性爱 |