Java并發(fā):五種線程安全類型、線程安全的實(shí)現(xiàn)、枚舉類型
來(lái)源:blog.csdn.net/u014454538/
article/details/98515807
1. Java中的線程安全
Java線程安全:狹義地認(rèn)為是多線程之間共享數(shù)據(jù)的訪問(wèn)。 Java語(yǔ)言中各種操作共享的數(shù)據(jù)有5種類型:不可變、絕對(duì)線程安全、相對(duì)線程安全、線程兼容、線程獨(dú)立
① 不可變
不可變(Immutable) 的對(duì)象一定是線程安全的,不需要再采取任何的線程安全保障措施。 只要能正確構(gòu)建一個(gè)不可變對(duì)象,該對(duì)象永遠(yuǎn)不會(huì)在多個(gè)線程之間出現(xiàn)不一致的狀態(tài)。 多線程環(huán)境下,應(yīng)當(dāng)盡量使對(duì)象成為不可變,來(lái)滿足線程安全。
如何實(shí)現(xiàn)不可變?
如果共享數(shù)據(jù)是基本數(shù)據(jù)類型,使用final關(guān)鍵字對(duì)其進(jìn)行修飾,就可以保證它是不可變的。 如果共享數(shù)據(jù)是一個(gè)對(duì)象,要保證對(duì)象的行為不會(huì)對(duì)其狀態(tài)產(chǎn)生任何影響。 String是不可變的,對(duì)其進(jìn)行substring()、replace()、concat()等操作,返回的是新的String對(duì)象,原始的String對(duì)象的值不受影響。而如果對(duì)StringBuffer或者StringBuilder對(duì)象進(jìn)行substring()、replace()、append()等操作,直接對(duì)原對(duì)象的值進(jìn)行改變。 要構(gòu)建不可變對(duì)象,需要將內(nèi)部狀態(tài)變量定義為final類型。如 java.lang.Integer類中將value定義為final類型。
private?final?int?value;
常見(jiàn)的不可變的類型:
final關(guān)鍵字修飾的基本數(shù)據(jù)類型 枚舉類型、String類型 常見(jiàn)的包裝類型:Short、Integer、Long、Float、Double、Byte、Character等 大數(shù)據(jù)類型:BigInteger、BigDecimal
注意:原子類 AtomicInteger 和 AtomicLong 則是可變的。
對(duì)于集合類型,可以使用?Collections.unmodifiableXXX()?方法來(lái)獲取一個(gè)不可變的集合。
通過(guò) Collections.unmodifiableMap(map)獲的一個(gè)不可變的Map類型。Collections.unmodifiableXXX()?先對(duì)原始的集合進(jìn)行拷貝,需要對(duì)集合進(jìn)行修改的方法都直接拋出異常。
例如,如果獲得的不可變map對(duì)象進(jìn)行put()、remove()、clear()操作,則會(huì)拋出UnsupportedOperationException異常。
② 絕對(duì)線程安全
絕對(duì)線程安全的實(shí)現(xiàn),通常需要付出很大的、甚至不切實(shí)際的代價(jià)。
Java API中提供的線程安全,大多數(shù)都不是絕對(duì)線程安全。
例如,對(duì)于數(shù)組集合Vector的操作,如get()、add()、remove()都是有synchronized關(guān)鍵字修飾。有時(shí)調(diào)用時(shí)也需要手動(dòng)添加同步手段,保證多線程的安全。
下面的代碼看似不需要同步,實(shí)際運(yùn)行過(guò)程中會(huì)報(bào)錯(cuò)。
import?java.util.Vector;
/**
?*?@Author:?lucy
?*?@Version?1.0
?*/
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?+?"個(gè)元素:?"?+?vector.get(i));
????????????????????}
????????????????}
????????????}).start();
????????????new?Thread(new?Runnable()?{
????????????????@Override
????????????????public?void?run()?{
????????????????????for?(int?i=0;i????????????????????????System.out.println("刪除vector中的第"?+?i+"個(gè)元素");
????????????????????????vector.remove(i);
????????????????????}
????????????????}
????????????}).start();
????????????while?(Thread.activeCount()>20)
????????????????return;
????????}
????}
}
出現(xiàn)ArrayIndexOutOfBoundsException異常,原因:某個(gè)線程恰好刪除了元素i,使得當(dāng)前線程無(wú)法訪問(wèn)元素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)
需要將對(duì)元素的get和remove構(gòu)造成同步代碼塊:
synchronized?(vector){
????for?(int?i?=?0;?i?????????System.out.println("獲取vector的第"?+?i?+?"個(gè)元素:?"?+?vector.get(i));
????}
}
synchronized?(vector){
????for?(int?i=0;i????????System.out.println("刪除vector中的第"?+?i+"個(gè)元素");
????????vector.remove(i);
????}
}
③ 相對(duì)線程安全
相對(duì)線程安全需要保證對(duì)該對(duì)象的單個(gè)操作是線程安全的,在必要的時(shí)候可以使用同步措施實(shí)現(xiàn)線程安全。 大部分的線程安全類都屬于相對(duì)線程安全,如Java容器中的Vector、HashTable、通過(guò) Collections.synchronizedXXX()方法包裝的集合。
④ 線程兼容
Java中大部分的類都是線程兼容的,通過(guò)添加同步措施,可以保證在多線程環(huán)境中安全使用這些類的對(duì)象。 如常見(jiàn)的ArrayList、HashTableMap都是線程兼容的。
⑤ 線程對(duì)立
線程對(duì)立是指:無(wú)法通過(guò)添加同步措施,實(shí)現(xiàn)多線程中的安全使用。 線程對(duì)立的常見(jiàn)操作有:Thread類的suspend()和resume()(已經(jīng)被JDK聲明廢除), System.setIn()和System.setOut()等。
2. Java的枚舉類型
通過(guò)enum關(guān)鍵字修飾的數(shù)據(jù)類型,叫枚舉類型。
枚舉類型的每個(gè)元素都有自己的序號(hào),通常從0開(kāi)始編號(hào)。 可以通過(guò)values()方法遍歷枚舉類型,通過(guò)name()或者toString()獲取枚舉類型的名稱 通過(guò)ordinal()方法獲取枚舉類型中元素的序號(hào)
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類型的成員變量,便可以為枚舉類型中的元素賦初值。
要想獲取枚舉類型中元素實(shí)際值,需要為成員變量添加getter方法。
雖然枚舉類型的元素有了自己的實(shí)際值,但是通過(guò)ordinal()方法獲取的元素序號(hào)不會(huì)發(fā)生改變。
public?class?EnumData?{
????public?static?void?main(String[]?args)?{
????????for?(Family?family?:?Family.values())?{
????????????System.out.println(family.name()?+?":實(shí)際值"?+?family.getValue()?+
????????????????????",?實(shí)際序號(hào)"?+?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線程安全的實(shí)現(xiàn)
① 互斥同步
互斥同步(Mutex Exclusion & Synchronization)是一種常見(jiàn)的并發(fā)正確性保障手段。
同步:多個(gè)線程并發(fā)訪問(wèn)共享數(shù)據(jù),保證共享數(shù)據(jù)同一時(shí)刻只被一個(gè)(或者一些,使用信號(hào)量)線程使用。 互斥:互斥是實(shí)現(xiàn)同步的一種手段,主要的互斥實(shí)現(xiàn)方式:臨界區(qū)(Critical Section)、互斥量(Mutex)、信號(hào)量(Semaphore)。
同步與互斥的關(guān)系:
互斥是原因,同步是結(jié)果。 同步是目的,互斥是方法。
Java中,最基本的實(shí)現(xiàn)互斥同步的手段是synchronized關(guān)鍵字,其次是JUC包中的ReentrantLock。
關(guān)于synchronized關(guān)鍵字:
編譯后的同步塊,開(kāi)始處會(huì)添加monitorenter指令,結(jié)束處或異常處會(huì)添加monitorexit指令。 monitorenter和monitorexit指令中都包含一個(gè)引用類型的參數(shù),分別指向加鎖或解鎖的對(duì)象。如果是同步代碼塊,則為synchronized括號(hào)中明確指定的對(duì)象;如果為普通方法,則為當(dāng)前實(shí)例對(duì)象;如果為靜態(tài)方法,則為類對(duì)應(yīng)的class對(duì)象。 JVM執(zhí)行monitorenter指令時(shí),要先嘗試獲取鎖:如果對(duì)象沒(méi)被鎖定或者當(dāng)前線程已經(jīng)擁有該對(duì)象的鎖,則鎖計(jì)數(shù)器加1;否則獲取鎖失敗,進(jìn)入阻塞狀態(tài),等待持有鎖的線程釋放鎖。 JVM執(zhí)行monitorexit指令時(shí),鎖計(jì)數(shù)器減1,直到計(jì)數(shù)器的值為0,鎖被釋放。(synchronized是支持重進(jìn)入的) 由于阻塞或者喚醒線程都需要從用戶態(tài)(User Mode)切換到核心態(tài)(Kernel Mode),有時(shí)鎖只會(huì)被持有很短的時(shí)間,沒(méi)有必要進(jìn)行狀態(tài)轉(zhuǎn)換。可以讓線程在阻塞之前先自旋等待一段時(shí)間,超時(shí)未獲取到鎖才進(jìn)入阻塞狀態(tài),這樣可以避免頻繁的切入到核心態(tài)。其實(shí),就是后面自旋鎖的思想。
關(guān)于ReentrantLock:
與synchronized關(guān)鍵字相比,它是API層面的互斥鎖(lock()、unlock()、try...finally)。 與synchronized關(guān)鍵字相比,具有可中斷、支持公平與非公平性、可綁定多個(gè)Condition對(duì)象的高級(jí)功能。 由于synchronized關(guān)鍵字被優(yōu)化,二者的性能差異并不是很大,如果不是想使用ReentrantLock的高級(jí)功能,優(yōu)先考慮使用synchronized關(guān)鍵字。
② 非阻塞同步
(1)CAS概述
互斥同步最大的性能問(wèn)題是線程的阻塞和喚醒,因此又叫阻塞同步。
互斥同步采用悲觀并發(fā)策略:
多線程并發(fā)訪問(wèn)共享數(shù)據(jù)時(shí),總是認(rèn)為只要不加正確的同步措施,肯定會(huì)出現(xiàn)問(wèn)題。 無(wú)論共享數(shù)據(jù)是否存在競(jìng)爭(zhēng),都會(huì)執(zhí)行加鎖、用戶態(tài)和心態(tài)的切換、維護(hù)鎖計(jì)數(shù)器、檢查是否有被阻塞的線程需要喚醒等操作。
隨著硬件指令集的發(fā)展,我們可以采用基于沖突檢測(cè)的樂(lè)觀并發(fā)策略:
先進(jìn)行操作,如果不存在沖突(即沒(méi)有其他線程爭(zhēng)用共享數(shù)據(jù)),則操作成功。 如果有其他線程爭(zhēng)用共享數(shù)據(jù),產(chǎn)生了沖突,使用其他的補(bǔ)償措施。 常見(jiàn)的補(bǔ)償措施:不斷嘗試,直到成功為止,比如循環(huán)的CAS操作。
樂(lè)觀并發(fā)策略的許多實(shí)現(xiàn)都不需要將線程阻塞,這種同步操作叫做非阻塞同步。
非阻塞同步依靠的硬件指令集:前三條是比較久遠(yuǎn)的指令,后兩條是現(xiàn)代處理器新增的。
測(cè)試和設(shè)置(Test and Set) 獲取并增加(Fetch and Increment) 交換(Swap) 比較并交換(Compare and Swap,即CAS) 加載鏈接/條件存儲(chǔ)(Load Linked/ Store Conditional,即LL/SC)
什么是CAS?
CAS,即Compare and Swap,需要借助處理器的cmpxchg指令完成。 CAS指令需要三個(gè)操作數(shù):內(nèi)存位置V(Java中可以簡(jiǎn)單的理解為變量的內(nèi)存地址)、舊的期待值A(chǔ)、新值B。 CAS指令執(zhí)行時(shí),當(dāng)且僅當(dāng)V符合舊的預(yù)期值A(chǔ),處理器才用新值B更新V的值;否則,不執(zhí)行更新。 不管是否更新V的值,都返回V的舊值,整個(gè)處理過(guò)程是一個(gè)原子操作。
原子操作:所謂的原子操作是指一個(gè)或一系列不可被中斷的操作。
Java中的CAS操作:
Java中的CAS操作由 sun.misc.Unsafe中的compareAndSwapInt()、compareAndSwapLong()等幾個(gè)方法包裝提供。實(shí)際無(wú)法調(diào)用這些方法,需要采用反射機(jī)制才能使用。在實(shí)際的開(kāi)發(fā)過(guò)程中,一般通過(guò)其他的Java API調(diào)用它們,如JUC包原子類中的 compareAndSet(expect, update)?、getAndIncrement()等方法。這些方法內(nèi)部都使用了Unsafe類的CAS操作。Unsafe類的CAS操作,通過(guò)JVM的即時(shí)編譯器編譯后,是一條與平臺(tái)相關(guān)的CAS指令。
除了偏向鎖,Java中其他鎖的實(shí)現(xiàn)方式都是用了循環(huán)的CAS操作。學(xué)習(xí)資料:Java進(jìn)階視頻資源
(2)通過(guò)循環(huán)的CAS實(shí)現(xiàn)原子操作
通過(guò)++i或者i++可以實(shí)現(xiàn)計(jì)數(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)用計(jì)數(shù)器i,運(yùn)行后的值為:?"?+?counter.i);
????}
????public?void?count()?{
????????i++;
????}
}
運(yùn)行以上的代碼發(fā)現(xiàn):當(dāng)線程數(shù)量增加,每個(gè)線程調(diào)用計(jì)數(shù)器的次數(shù)變大時(shí),每次運(yùn)行的結(jié)果是錯(cuò)誤且不固定的。


為了實(shí)現(xiàn)實(shí)在一個(gè)多線程環(huán)境下、線程安全的計(jì)數(shù)器,需要使用AtomicInteger的原子自增運(yùn)算。
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)用線程安全的計(jì)數(shù)器atomic:"+counter.atomic);
????}
????public?void?count()?{
????????//?調(diào)用compareAnSet方法,使用循環(huán)的CAS操作實(shí)現(xiàn)計(jì)數(shù)器的原子自增
????????for?(;?;?)?{
????????????int?expect?=?atomic.get();
????????????int?curVal?=?expect?+?1;
????????????if?(atomic.compareAndSet(expect,?curVal))?{
????????????????break;
????????????}
????????}
????}
}

與非線程安全的計(jì)數(shù)器相比,線程安全的計(jì)數(shù)器有以下特點(diǎn):
將int類型的計(jì)數(shù)器變量i,更換成具有CAS操作的AtomicInteger類型的計(jì)數(shù)器變量atomic。 進(jìn)行自增運(yùn)算時(shí),通過(guò)循環(huán)的CAS操作實(shí)現(xiàn)atomic的原子自增。 先通過(guò)atomic.get()獲取expect的值,將expect加一得到新值,然后通過(guò) atomic.compareAndSet(expect, curVal)這一方法實(shí)現(xiàn)CAS操作。其中compareAndSet()返回的true或者false,表示此次CAS操作是否成功。如果返回false,則不停地重復(fù)執(zhí)行CAS操作,直到操作成功。
上面的count方法實(shí)現(xiàn)的AtomicInteger原子自增,可以只需要調(diào)用incrementAndGet()一個(gè)方法就能實(shí)現(xiàn)。
public?void?count()?{
????//?調(diào)用incrementAndGet方法,實(shí)現(xiàn)AtomicInteger的原子自增
????atomic.incrementAndGet();
}
因?yàn)閕ncrementAndGet()方法,封裝了通過(guò)循環(huán)的CAS操作實(shí)現(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操作存在的問(wèn)題
1. ABA問(wèn)題
在執(zhí)行CAS操作更新共享變量的值時(shí),如果一個(gè)值原來(lái)是A,被其他線程改成了B,然后又改回成了A。對(duì)于該CAS操作來(lái)說(shuō),它完全感受不到共享變量值的變化。這種操作漏洞稱為CAS操作的ABA問(wèn)題。 解決該問(wèn)題的思路是,為變量添加版本號(hào),每次更新時(shí)版本號(hào)遞增。這種場(chǎng)景下就成了1A --> 2B --> 3A。CAS操作就能檢測(cè)到共享變量的ABA問(wèn)題了。 JUC包中,也提供了相應(yīng)的帶標(biāo)記的原子引用類AtomicStampedReference來(lái)解決ABA問(wèn)題。 AtomicStampedReference的compareAndSet()方法會(huì)首先比較期待的引用是否等于當(dāng)前引用,然后檢查期待的標(biāo)記是否等于當(dāng)前標(biāo)記。如果全部相等,則以原子操作的方式將新的引用和新的標(biāo)記更新到當(dāng)前值中。 但是AtomicStampedReference目前比較雞肋,如果想解決AB問(wèn)題,可以使用鎖。
2. 循環(huán)時(shí)間過(guò)長(zhǎng),開(kāi)銷大
循環(huán)的CAS操作如果長(zhǎng)時(shí)間不成功,會(huì)給CPU帶來(lái)非常大的執(zhí)行開(kāi)銷。
3. 只能保證一個(gè)共享變量的原子操作
只對(duì)一個(gè)共享變量執(zhí)行操作時(shí),可以通過(guò)循環(huán)的CAS操作實(shí)現(xiàn)。如果是多個(gè)共享變量,循環(huán)的CAS操作無(wú)法保證操作的原子性。 取巧的操作:將多個(gè)共享變量合為一個(gè)變量進(jìn)行CAS操作。JDK1.5開(kāi)始,提供了AtomicReference類保證引用對(duì)象之間的原子性,可以將多個(gè)變量放在一個(gè)對(duì)象中進(jìn)行CAS操作。
③ 無(wú)同步方案
同步只是保證共享數(shù)據(jù)爭(zhēng)用時(shí)正確性的一種手段,如果不存在共享數(shù)據(jù),自然無(wú)須任何同步措施。
(1)棧封閉
多個(gè)線程訪問(wèn)同一個(gè)方法的局部變量時(shí),不會(huì)出現(xiàn)線程安全問(wèn)題。
因?yàn)榉椒ㄖ械木植孔兞坎粫?huì)逃出該方法而被其他線程訪問(wèn),因此可以看做JVM棧中數(shù)據(jù),屬于線程私有。
(2)可重入代碼(Reentrant Code)
可重入代碼又叫純代碼(Pure Code),可在代碼執(zhí)行的任何時(shí)候中斷他它,轉(zhuǎn)去執(zhí)行另外一段代碼(包括遞歸調(diào)用它本身),控制權(quán)返回后,原來(lái)的程序不會(huì)出現(xiàn)任何錯(cuò)誤。
所有可重入的代碼都是線程安全,并非所有線程安全的代碼都是可重入的。
可重入代碼的共同特征:
不依賴存儲(chǔ)在堆上的數(shù)據(jù)和公用的系統(tǒng)資源 用到的狀態(tài)量都由參數(shù)中傳入 不調(diào)用非可重用的方法
如何判斷代碼是否具備可重入性?如果一個(gè)方法,它的返回結(jié)果是可預(yù)測(cè)的。只要輸入了相同的數(shù)據(jù),就都能返回相同的結(jié)果,那它就滿足可重入性,當(dāng)然也就是線程安全的。
(3)線程本地存儲(chǔ)(TLS)
線程本地存儲(chǔ)(Thread Local Storage):
如果一段代碼中所需要的數(shù)據(jù)必須與其他代碼共享,那就看看這些共享數(shù)據(jù)的代碼是否能保證在同一個(gè)線程中執(zhí)行。 如果能保證,我們就可以把共享數(shù)據(jù)的可見(jiàn)范圍限制在同一個(gè)線程內(nèi)。 這樣,無(wú)須同步也能保證線程之間不出現(xiàn)數(shù)據(jù)爭(zhēng)用的問(wèn)題。
TLS的重要應(yīng)用實(shí)例:經(jīng)典的Web交互模型中,一個(gè)請(qǐng)求對(duì)應(yīng)一個(gè)服務(wù)器線程,使得Web服務(wù)器應(yīng)用可以使用。學(xué)習(xí)資料:Java進(jìn)階視頻資源
Java中沒(méi)有關(guān)鍵字可以將一個(gè)變量定義為線程所獨(dú)享,但是Java中創(chuàng)建了java.lang.ThreadLocal類提供線程本地存儲(chǔ)功能。
每一個(gè)線程內(nèi)部都包含一個(gè)ThreadLocalMap對(duì)象,該對(duì)象將ThreadLocal對(duì)象的hashCode值作為key,即ThreadLocal.threadLocalHashCode,將本地線程變量作為value,構(gòu)成鍵值對(duì)。 ThreadLocal對(duì)象是當(dāng)前線程ThreadLocalMap對(duì)象的訪問(wèn)入口,通過(guò) threadLocal.set()為本地線程添加獨(dú)享變量;通過(guò)threadLocal.get()獲取本地線程獨(dú)享變量的值。ThreadLocal、ThreadLocalMap、Thread的關(guān)系:Thread對(duì)象中包含ThreadLocalMap對(duì)象,ThreadLocalMap對(duì)象中包含多個(gè)鍵值對(duì),每個(gè)鍵值對(duì)的key是ThreadLocal對(duì)象的hashCode,value是本地線程變量。

ThreadLocal的編程實(shí)例:
想為某個(gè)線程添加本地線程變量,必須通過(guò)ThreadLocal對(duì)象在該線程中進(jìn)行添加,構(gòu)造出的鍵值對(duì)自動(dòng)存入該線程的map中; 想要獲取某個(gè)線程的本地線程變量,必須在該線程中獲取,會(huì)自動(dòng)查詢?cè)摼€程的map,獲得ThreadLocal對(duì)象對(duì)應(yīng)的value。 通過(guò)ThreadLocal對(duì)象重復(fù)為某個(gè)線程添加鍵值對(duì),會(huì)覆蓋之前的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è)置當(dāng)前線程的本地線程變量
????????????????threadLocal1.set("thread1");
????????????????threadLocal2.set(1);
????????????????System.out.println(threadLocal1.get()?+?":?"?+?threadLocal2.get());
????????????????//?使用完畢后要?jiǎng)h除,避免內(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();
????????//?沒(méi)有通過(guò)ThreadLocal為主線程添加過(guò)本地線程變量,獲取到的內(nèi)容都是null
????????System.out.println(threadLocal1.get()+":?"+threadLocal2.get());
????}
}

對(duì)ThreadLocal的正確理解:
ThreadLocal適用于線程需要有自己的實(shí)例變量,該實(shí)例變量可以在多個(gè)方法中被使用,但是不能被其他線程共享的場(chǎng)景。 由于不存在數(shù)據(jù)共享,何談同步?因此ThreadLocal 從理論上講,不是用來(lái)解決多線程并發(fā)問(wèn)題的。
ThreadLocal的實(shí)現(xiàn):
最原始的想法:ThreadLocal維護(hù)線程與實(shí)例的映射。既然通過(guò)ThreadLocal對(duì)象為線程添加本地線程變量,那就將ThreadLocalMap放在ThreadLocal中。

原始想法存在的缺陷:多線程并發(fā)訪問(wèn)ThreadLocal中的Map,需要添加鎖。這是, JDK 未采用該方案的一個(gè)原因。
優(yōu)化后的方法:Thread維護(hù)ThreadLocal與實(shí)例的映射。Map是每個(gè)線程所私有,只能在當(dāng)前線程通過(guò)ThreadLocal對(duì)象訪問(wèn)自身的Map。不存在多線程并發(fā)訪問(wèn)同一個(gè)Map的情況,也就不需要鎖。
優(yōu)化后存在內(nèi)存泄露的情況:JDK1.8中,ThreadLocalMap每個(gè)Entry對(duì)ThreadLocal對(duì)象是弱引用,對(duì)每個(gè)實(shí)例是強(qiáng)引用。當(dāng)ThreadLocal對(duì)象被回收后,該Entry的鍵變成null,但Entry無(wú)法被移除。使得實(shí)例被Entry引用無(wú)法回收,造成內(nèi)存泄露。
END
推薦閱讀 一鍵生成Springboot & Vue項(xiàng)目!【畢設(shè)神器】
Java可視化編程工具系列(一)
Java可視化編程工具系列(二)
順便給大家推薦一個(gè)GitHub項(xiàng)目,這個(gè) GitHub 整理了上千本常用技術(shù)PDF,絕大部分核心的技術(shù)書(shū)籍都可以在這里找到,
GitHub地址:https://github.com/javadevbooks/books
Gitee地址:https://gitee.com/javadevbooks/books
電子書(shū)已經(jīng)更新好了,你們需要的可以自行下載了,記得點(diǎn)一個(gè)star,持續(xù)更新中..

