面試:再見(jiàn)多線程!
??Java大聯(lián)盟 ? 致力于最高效的Java學(xué)習(xí)
關(guān)注
B 站搜索:楠哥教你學(xué)Java
獲取更多優(yōu)質(zhì)視頻教程
應(yīng)用場(chǎng)景
1、異步處理,例如:發(fā)郵件、發(fā)微博、寫日志等;
2、分布式計(jì)算
3、定期執(zhí)行一些特殊任務(wù):如定期更新配置文件,任務(wù)調(diào)度(如quartz),一些監(jiān)控用于定期信息采集等
4、TOMCAT處理多用戶請(qǐng)求。
5、針對(duì)特別耗時(shí)的操作。多線程同步執(zhí)行可以提高速度。例如:定時(shí)向大量(100w以上)的用戶發(fā)送郵件。
多線程編程面臨的挑戰(zhàn)及解決思路
問(wèn)題一:上下文切換
并發(fā)不一定快于串行,因?yàn)闀?huì)有切換上下文的開(kāi)銷。【切換上下文:?jiǎn)魏瞬l(fā)時(shí),cpu會(huì)使用時(shí)間片輪轉(zhuǎn)實(shí)現(xiàn)并發(fā),每一次輪轉(zhuǎn),會(huì)保留當(dāng)前執(zhí)行的狀態(tài)】。
解決上下文切換開(kāi)銷的辦法:
無(wú)鎖并發(fā)編程:多線程競(jìng)爭(zhēng)鎖時(shí),會(huì)引起上下文切換,所以多線程處理數(shù)據(jù)時(shí),可以用一些辦法來(lái)避免使用鎖,如將數(shù)據(jù)的ID按照Hash算法取模分段,不同的線程處理不同段的數(shù)據(jù)。
CAS算法:Java的Atomic包使用CAS算法來(lái)更新數(shù)據(jù),而不需要加鎖。
使用最少線程:避免創(chuàng)建不需要的線程,比如任務(wù)很少,但是創(chuàng)建了很多線程來(lái)處理,這樣會(huì)造成大量線程都處于等待狀態(tài)。
協(xié)程:在單線程里實(shí)現(xiàn)多任務(wù)的調(diào)度,并在單線程里維持多個(gè)任務(wù)間的切換。
問(wèn)題二:死鎖
死鎖是一個(gè)比較常見(jiàn)也比較難解決的問(wèn)題,當(dāng)多個(gè)線程等待同一個(gè)不會(huì)釋放的資源時(shí),就會(huì)發(fā)生死鎖。避免死鎖可以參考下面的思路。
避免死鎖的方法:
避免一個(gè)線程同時(shí)獲取多個(gè)鎖。
避免一個(gè)線程在鎖內(nèi)同時(shí)占用多個(gè)資源,盡量保證每個(gè)鎖只占用一個(gè)資源。
嘗試使用定時(shí)鎖,使用lock.tryLock(timeout)來(lái)替代使用內(nèi)部鎖機(jī)制。
對(duì)于數(shù)據(jù)庫(kù)鎖,加鎖和解鎖必須在一個(gè)數(shù)據(jù)庫(kù)連接里,否則會(huì)出現(xiàn)解鎖失敗的情況。
問(wèn)題三:資源限制
資源限制是指在進(jìn)行并發(fā)編程時(shí),程序的執(zhí)行速度受限于計(jì)算機(jī)硬件資源或軟件資源。例如,服務(wù)器的帶寬只有2Mb/s,某個(gè)資源的下載速度是1M每秒,系統(tǒng)啟動(dòng)10個(gè)線程下載資源,下載速度不會(huì)變成10Mb/s
解決資源限制思路:
對(duì)于硬件資源限制,可以考慮使用集群并行執(zhí)行程序。
對(duì)于軟件資源限制,可以考慮使用資源池將資源復(fù)用。比如使用連接池將數(shù)據(jù)庫(kù)和Socket連接復(fù)用,或者在調(diào)用對(duì)方webservice接口獲取數(shù)據(jù)時(shí),只建立一個(gè)連接。
基礎(chǔ)示例
實(shí)現(xiàn)多線程基本的實(shí)現(xiàn)方式就是如下兩種:
繼承Thread類;
實(shí)現(xiàn)Runnable接口;
實(shí)際使用時(shí),會(huì)用到線程池,還會(huì)用spring管理線程池,下面使用多線程完成幾個(gè)小例子。
示例一:多線程使用reentrantLock實(shí)現(xiàn)交替打印奇數(shù)偶數(shù),代碼見(jiàn)壓縮包:
示例二:4個(gè)線程,兩個(gè)存錢,兩個(gè)取錢
示例三:spring管理線程池配置
停止線程
終止線程有三種方式:
(1)使用退出標(biāo)志,run()執(zhí)行完以后退出【拋出異常或者return】
(2)使用stop強(qiáng)行停止線程,不推薦,會(huì)導(dǎo)致當(dāng)前任務(wù)執(zhí)行到一半突然中斷,出現(xiàn)不可預(yù)料的問(wèn)題;而且stop和suspend以及resume一樣是過(guò)期作廢的方法
(3)使用interrupt中斷線程
interrupt()方法不會(huì)真的停止線程,而是會(huì)記錄一個(gè)標(biāo)志,這個(gè)標(biāo)志,可以由下面的兩個(gè)方法檢測(cè)到。
Thread.interrupted( ) 測(cè)試當(dāng)前線程是否停止,但是它具有清除線程中斷狀態(tài)功能,如第一次返回true,第二次調(diào)用會(huì)返回false;
Thread.isInterrupted( ),僅返回結(jié)果,不清除狀態(tài)。重復(fù)調(diào)用會(huì)結(jié)果一致。
基于上面的邏輯,可以根據(jù)標(biāo)志來(lái)在run()里面狀態(tài),然后再使用interrupt()來(lái)使代碼停止,停止代碼可以使用拋出異常的方式。
如果在sleep里面拋出異常停止線程,會(huì)進(jìn)入catch,并清除停止?fàn)顟B(tài),使之變成false;
stop()暴力停止,已經(jīng)被作廢,建議不使用;
使用stop的方法帶來(lái)的問(wèn)題:
1.執(zhí)行到一半強(qiáng)制停止,可能清理工作來(lái)不及;
2.對(duì)鎖定的對(duì)象進(jìn)行了解鎖,導(dǎo)致數(shù)據(jù)不同步,不一致。
return方法停止線程:
其實(shí)就是使用 打標(biāo)記+return 替換 打標(biāo)記+拋異常
暫停線程與恢復(fù)線程
suspend()暫停,resume()恢復(fù),已經(jīng)被棄用,
缺點(diǎn):
獨(dú)占,使用不當(dāng)很容易讓公共的同步對(duì)象獨(dú)占,使得其他線程無(wú)法訪問(wèn)。
不同步:線程暫停容易導(dǎo)致不同步。
yield():作用是放棄當(dāng)前cpu資源,將他讓給其他任務(wù)去占用cpu;但是放棄的時(shí)間不確定,有可能剛放棄,馬上又獲得cpu時(shí)間片;直接在run方法里面使用即可。 線程優(yōu)先級(jí)
多個(gè)線程可以設(shè)置優(yōu)先級(jí)。
優(yōu)先級(jí)設(shè)置:
setPriority( ) 方法;分為1-10,10個(gè)等級(jí),超過(guò)這個(gè)范圍,會(huì)拋出異常。
java線程優(yōu)先級(jí)可以繼承,A線程啟動(dòng)B線程,那么B與A的優(yōu)先級(jí)是一樣的。
優(yōu)先級(jí)高的絕大多數(shù)會(huì)先執(zhí)行,但結(jié)果不是百分之百的。
對(duì)象以及變量訪問(wèn)
在run里面執(zhí)行的方法,如果是同步的,則不會(huì)有線程安全問(wèn)題,使用synchronized關(guān)鍵字即可保證同步。
synchronized持有的鎖是對(duì)象鎖,如果多個(gè)線程訪問(wèn)多個(gè)對(duì)象,則JVM會(huì)創(chuàng)建多個(gè)鎖。【多個(gè)對(duì)象,多個(gè)鎖,此處對(duì)象是指加了synchronized關(guān)鍵字的方法所在的類也就是創(chuàng)建線程時(shí)傳入的對(duì)象,例如:
Thread a = new Thread(object1);
Thread b= new Thread(object2);
a.start();
b.start();
這種情況下線程a和b持有的是兩個(gè)不同的鎖。
贓讀
讀取全局變量時(shí),此變量已經(jīng)被其他線程修改過(guò)了,就會(huì)出現(xiàn)贓讀。
synchronized 實(shí)際上是對(duì)象鎖
現(xiàn)有A,B兩個(gè)線程,C對(duì)象,C擁有加了synchronized關(guān)鍵字的方法X1()和X2(),以及未加synchronized關(guān)鍵字的X3()方法。
當(dāng)A線程訪問(wèn)X1方法時(shí),B線程想訪問(wèn)X1,必須等待A執(zhí)行完,釋放對(duì)象鎖;
當(dāng)A在訪問(wèn)X1,B想訪問(wèn)X3(),無(wú)需等待,直接訪問(wèn)。
當(dāng)A在訪問(wèn)X1,B想訪問(wèn)X2(),需要等待A執(zhí)行完。
synchronized 鎖重入
在synchronized方法內(nèi),調(diào)用本類的其他的synchronized方法時(shí),總是可以成功。
如果不可重入的話,會(huì)造成死鎖;
可重入鎖,支持在父子類繼承的環(huán)境:子類可以通過(guò)"可重入鎖"調(diào)用父類的同步方法。
異常會(huì)釋放鎖
當(dāng)一個(gè)線程執(zhí)行出現(xiàn)異常,會(huì)釋放他所持有的所有鎖。
同步不具有繼承性
父類中A()方法是synchronized的,子類中的A方法,不會(huì)是同步的,需要手動(dòng)加上。
synchronized 同步語(yǔ)句塊
synchronized(this){//...同步的代碼塊...}
synchronized聲明方法的弊端:
A線程調(diào)用同步方法執(zhí)行長(zhǎng)時(shí)間任務(wù)時(shí),B線程需要等待很久。
解決辦法:可以使用synchronized同步語(yǔ)句塊。
synchronized可以修飾代碼塊。使用synchronized修飾需要保持同步部分代碼,其余部分異步,借此提高運(yùn)行效率。
synchronized 代碼塊間的同步性
A對(duì)象,擁有X1和X2兩個(gè)synchronized 同步代碼塊,
那么,B線程在訪問(wèn)X1時(shí),C線程也無(wú)法訪問(wèn)X2,需要等待B線程釋放對(duì)象鎖。
此處與synchronized 修飾方法時(shí)一樣。他們持有的都是對(duì)象鎖。
任意對(duì)象作為監(jiān)視器
synchronized 修飾的代碼塊時(shí),如果傳入this,則會(huì)監(jiān)視當(dāng)前對(duì)象,加鎖時(shí)會(huì)對(duì)當(dāng)前整個(gè)對(duì)象加鎖;
例如:對(duì)象A有方法X1() 和X2() ,如果在X1和X2里有一段同步代碼塊,并且synchronized(this)傳入的都是this對(duì)象,那么在B線程訪問(wèn)X1的同步代碼塊時(shí),C線程也無(wú)法X2的同步代碼塊。
如果傳入的不是this,而是另外的對(duì)象,則C可以訪問(wèn)X2的同步代碼塊。
要保證傳入其他監(jiān)視對(duì)象時(shí)的成功同步,必須保證在調(diào)用時(shí),監(jiān)視對(duì)象是一致的,不能每次都new一個(gè)監(jiān)視對(duì)象,否則會(huì)導(dǎo)致變成異步的。
臟讀問(wèn)題
有時(shí)候,僅僅使用synchronized 修飾方法,并不能保證正確的邏輯。
比如,兩個(gè)synchronized 修飾的方法add() 與getSize() ,他們分別是對(duì)list進(jìn)行讀與寫的操作,此時(shí)兩個(gè)線程先后調(diào)用這兩個(gè)方法,會(huì)導(dǎo)致結(jié)果超出預(yù)期。
解決:
add()方法中,synchronized 改成去修飾代碼塊,并且傳入監(jiān)視對(duì)象list;
synchronized(list){//--- add ---}
靜態(tài)同步synchronized 方法,與synchronized(class)代碼塊
synchronized 加在static 靜態(tài)方法上,就是對(duì)當(dāng)前.java文件對(duì)應(yīng)的class類進(jìn)行持鎖。
synchronized static等同于synchronized (object.class) 可以對(duì)該類的所有對(duì)象起作用,即:即使需要new不同的對(duì)象,也可以保持同步。
String 的常量池特性
一般不使用String變量來(lái)作為鎖的監(jiān)視對(duì)象,當(dāng)對(duì)一個(gè)String變量持有鎖時(shí),如果兩個(gè)訪問(wèn)線程傳入的String變量值一樣,會(huì)導(dǎo)致鎖不被釋放,其中一個(gè)線程無(wú)法執(zhí)行。
可以使用對(duì)象來(lái)存儲(chǔ)相應(yīng)的變量解決此問(wèn)題。
volatile 關(guān)鍵字
一旦一個(gè)共享變量(類的成員變量、類的靜態(tài)成員變量)被volatile 修飾之后,那么就具備了兩層語(yǔ)義:
1)保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見(jiàn)性,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來(lái)說(shuō)是立即可見(jiàn)的。
2)禁止進(jìn)行指令重排序。
volatile 保證有序性
volatile關(guān)鍵字禁止指令重排序有兩層意思:
1)當(dāng)程序執(zhí)行到volatile變量的讀操作或者寫操作時(shí),在其前面的操作的更改肯定全部已經(jīng)進(jìn)行,且結(jié)果已經(jīng)對(duì)后面的操作可見(jiàn);在其后面的操作肯定還沒(méi)有進(jìn)行;
2)在進(jìn)行指令優(yōu)化時(shí),不能將在對(duì)volatile變量的讀操作或者寫操作的語(yǔ)句放在其后面執(zhí)行,也不能把volatile變量后面的語(yǔ)句放到其前面執(zhí)行。
可能上面說(shuō)的比較繞,舉個(gè)簡(jiǎn)單的例子:
//x、y為非volatile變量//flag為volatile變量x = 2; //語(yǔ)句1y = 0; //語(yǔ)句2flag = true; //語(yǔ)句3x = 4; //語(yǔ)句4y = -1; //語(yǔ)句5
由于flag變量為volatile變量,那么在進(jìn)行指令重排序的過(guò)程的時(shí)候,不會(huì)將語(yǔ)句3放到語(yǔ)句1、語(yǔ)句2前面,也不會(huì)講語(yǔ)句3放到語(yǔ)句4、語(yǔ)句5后面。但是要注意語(yǔ)句1和語(yǔ)句2的順序、語(yǔ)句4和語(yǔ)句5的順序是不作任何保證的。
并且volatile關(guān)鍵字能保證,執(zhí)行到語(yǔ)句3時(shí),語(yǔ)句1和語(yǔ)句2必定是執(zhí)行完畢了的,且語(yǔ)句1和語(yǔ)句2的執(zhí)行結(jié)果對(duì)語(yǔ)句3、語(yǔ)句4、語(yǔ)句5是可見(jiàn)的。
那么我們回到前面舉的一個(gè)例子:
//線程1:context =loadContext(); //語(yǔ)句1inited = true; //語(yǔ)句2//線程2:while(!inited ){sleep();}doSomethingwithconfig(context);
前面舉這個(gè)例子的時(shí)候,提到有可能語(yǔ)句2會(huì)在語(yǔ)句1之前執(zhí)行,那么久可能導(dǎo)致context還沒(méi)被初始化,而線程2中就使用未初始化的context去進(jìn)行操作,導(dǎo)致程序出錯(cuò)。
這里如果用volatile關(guān)鍵字對(duì)inited變量進(jìn)行修飾,就不會(huì)出現(xiàn)這種問(wèn)題了,因?yàn)楫?dāng)執(zhí)行到語(yǔ)句2時(shí),必定能保證context已經(jīng)初始化完畢。
與 synchronized 對(duì)比
volatile是線程同步的輕量實(shí)現(xiàn),只能修飾變量,性能高于synchronized
volatile保證可見(jiàn)性,不保證原子性【一旦其修飾的變量改變,其余的線程都能發(fā)現(xiàn),因?yàn)闀?huì)強(qiáng)制從公共堆棧取值】,synchronized保證原子性,間接保證可見(jiàn)性,因?yàn)樗麜?huì)將私有內(nèi)存和公共內(nèi)存的值同步
例如:i++操作,實(shí)際上不是原子操作,他有3步:
(1).從內(nèi)存取i值
(2).計(jì)算i的值
(3).將i的新值寫到內(nèi)存
多個(gè)線程執(zhí)行時(shí),使用volatile,可能導(dǎo)致數(shù)據(jù)臟讀,進(jìn)而出現(xiàn)錯(cuò)誤。
多線程訪問(wèn)volatile不會(huì)阻塞,而synchronized會(huì)
volatile是解決變量在多個(gè)線程之間的可見(jiàn)性,synchronized是保證多個(gè)線程之間資源的同步性。
volatile 的實(shí)現(xiàn)原理
1.可見(jiàn)性
處理器為了提高處理速度,不直接和內(nèi)存進(jìn)行通訊,而是將系統(tǒng)內(nèi)存的數(shù)據(jù)獨(dú)到內(nèi)部緩存后再進(jìn)行操作,但操作完后不知什么時(shí)候會(huì)寫到內(nèi)存。
如果對(duì)聲明了volatile變量進(jìn)行寫操作時(shí),JVM會(huì)向處理器發(fā)送一條Lock前綴的指令,將這個(gè)變量所在緩存行的數(shù)據(jù)寫會(huì)到系統(tǒng)內(nèi)存。這一步確保了如果有其他線程對(duì)聲明了volatile變量進(jìn)行修改,則立即更新主內(nèi)存中數(shù)據(jù)。
但這時(shí)候其他處理器的緩存還是舊的,所以在多處理器環(huán)境下,為了保證各個(gè)處理器緩存一致,每個(gè)處理會(huì)通過(guò)嗅探在總線上傳播的數(shù)據(jù)來(lái)檢查 自己的緩存是否過(guò)期,當(dāng)處理器發(fā)現(xiàn)自己緩存行對(duì)應(yīng)的內(nèi)存地址被修改了,就會(huì)將當(dāng)前處理器的緩存行設(shè)置成無(wú)效狀態(tài),當(dāng)處理器要對(duì)這個(gè)數(shù)據(jù)進(jìn)行修改操作時(shí),會(huì)強(qiáng)制重新從系統(tǒng)內(nèi)存把數(shù)據(jù)讀到處理器緩存里。這一步確保了其他線程獲得的聲明了volatile變量都是從主內(nèi)存中獲取最新的。
2.有序性
Lock前綴指令實(shí)際上相當(dāng)于一個(gè)內(nèi)存屏障(也成內(nèi)存柵欄),它確保指令重排序時(shí)不會(huì)把其后面的指令排到內(nèi)存屏障之前的位置,也不會(huì)把前面的指令排到內(nèi)存屏障的后面;即在執(zhí)行到內(nèi)存屏障這句指令時(shí),在它前面的操作已經(jīng)全部完成。
volatile 的應(yīng)用場(chǎng)景
synchronized關(guān)鍵字是防止多個(gè)線程同時(shí)執(zhí)行一段代碼,那么就會(huì)很影響程序執(zhí)行效率,而volatile關(guān)鍵字在某些情況下性能要優(yōu)于synchronized,但是要注意volatile關(guān)鍵字是無(wú)法替代synchronized關(guān)鍵字的,因?yàn)関olatile關(guān)鍵字無(wú)法保證操作的原子性。通常來(lái)說(shuō),使用volatile必須具備以下2個(gè)條件:
1)對(duì)變量的寫操作不依賴于當(dāng)前值
2)該變量沒(méi)有包含在具有其他變量的不變式中
下面列舉幾個(gè)Java中使用volatile的幾個(gè)場(chǎng)景:
①.狀態(tài)標(biāo)記量
volatile booleanflag = false;//線程1while(!flag){doSomething();}//線程2public voidsetFlag() {flag = true;}
根據(jù)狀態(tài)標(biāo)記,終止線程。
②.單例模式中的doublecheck
class Singleton {private volatile static Singleton instance= null;private Singleton() {}public static Singleton getInstance() {if(instance==null) {synchronized (Singleton.class) {if(instance==null)instance = new Singleton();}}return instance;}}
為什么要使用volatile 修飾instance?
主要在于instance= new Singleton()這句,這并非是一個(gè)原子操作,事實(shí)上在 JVM 中這句話大概做了下面 3 件事情:
1.給 instance 分配內(nèi)存
2.調(diào)用Singleton 的構(gòu)造函數(shù)來(lái)初始化成員變量
3.將instance對(duì)象指向分配的內(nèi)存空間(執(zhí)行完這步 instance就為非 null 了)。
但是在 JVM 的即時(shí)編譯器中存在指令重排序的優(yōu)化。也就是說(shuō)上面的第二步和第三步的順序是不能保證的,最終的執(zhí)行順序可能是 1-2-3 也可能是 1-3-2。如果是后者,則在 3 執(zhí)行完畢、2 未執(zhí)行之前,被線程二搶占了,這時(shí) instance已經(jīng)是非 null 了(但卻沒(méi)有初始化),所以線程二會(huì)直接返回 instance,然后使用,然后順理成章地報(bào)錯(cuò)。
線程間通信
1、等待通知機(jī)制
wait 使線程暫停,而notify 使線程繼續(xù)運(yùn)行。還有notifyAll() 方法。
wait()和notify(),兩個(gè)方法來(lái)實(shí)現(xiàn)等待通知機(jī)制;
注意:(1)兩個(gè)方法在調(diào)用時(shí)都需要持有當(dāng)前對(duì)象的對(duì)象鎖,所以都只能在同步代碼塊或者同步方法里面調(diào)用,如果不是會(huì)拋出異常。
2、wait
(2)wait方法會(huì)將當(dāng)前線程置入“預(yù)執(zhí)行隊(duì)列”,并在wait()所在代碼行停止執(zhí)行,直到接到notify(),或者被中斷;
(3)執(zhí)行wait()后,當(dāng)前線程釋放鎖;
3、notify
(1)如果多個(gè)線程在wait,那么會(huì)由線程規(guī)劃器,挑選一個(gè)執(zhí)行notify,并使他獲取該對(duì)象的對(duì)象鎖;
(2)noitfy執(zhí)行之后,當(dāng)前線程不會(huì)立馬釋放該對(duì)象鎖,wait狀態(tài)的線程也不能立馬獲得該對(duì)象鎖,要等執(zhí)行notify()方法的線程將程序執(zhí)行完,也就是退出synchronized代碼塊之后才會(huì)釋放鎖,并讓wait獲得。
(3)多個(gè)wait的線程,第一個(gè)獲取到notify并執(zhí)行完之后,其余的wait狀態(tài)的線程如果沒(méi)有被通知,還是會(huì)一直阻塞。
wait 之后自動(dòng)釋放鎖,notify 之后不會(huì)立馬釋放鎖
interrupt 方法與 wait
當(dāng)線程在wait狀態(tài)時(shí),調(diào)用對(duì)象的interrupt()方法,會(huì)拋出異常。
(1)執(zhí)行完同步代碼塊之后,會(huì)釋放當(dāng)前對(duì)象的鎖
(2)執(zhí)行同步代碼塊過(guò)程中,拋出異常也會(huì)釋放鎖
(3)執(zhí)行wait()之后,也會(huì)釋放鎖
wait(long)
執(zhí)行wait(5000)后,首先會(huì)等待5秒,如果5秒內(nèi)沒(méi)有收到通知,會(huì)自動(dòng)喚醒線程,退出wait狀態(tài)。
通過(guò)管道進(jìn)行線程間通信
4個(gè)類進(jìn)行線程間通信:
(1)字節(jié)流:PipedInputStream和PipedOuputStream
(2)字符流:PipedReader和PipedWriter
使用語(yǔ)法:
輸出:PipedOuputStream
PipedOuputStream out;
out.write();
out.close();
join 方法
在主線程中調(diào)用子線程的join方法,可以讓主線程等待子線程結(jié)束之后,
再開(kāi)始執(zhí)行join()之后的代碼。
join可以使線程排隊(duì)運(yùn)行,類似于synchronized的同步;區(qū)別在于join在內(nèi)部使用wait()等待,而synchronized使用對(duì)象監(jiān)視器原理同步。
注意
在join過(guò)程中,如果當(dāng)前線程對(duì)象被中斷,則當(dāng)前線程出現(xiàn)異常,子線程會(huì)繼續(xù)運(yùn)行;
join(long)
long參數(shù)是設(shè)定等待時(shí)間,使用sleep(long)也可以等待,但二者是有區(qū)別的:
join(long),內(nèi)部是使用的wait(long),等待時(shí)會(huì)釋放鎖;
sleep(long)等待時(shí)不會(huì)釋放鎖。
ThreadLocal
變量值的共享可以使用public static;
如果想讓每個(gè)線程都有自己的共享變量。可以使用ThreadLocal;ThreadLocal可以看做全局存放數(shù)據(jù)的盒子,盒子中可以存儲(chǔ)每個(gè)線程的私有數(shù)據(jù);
使用時(shí),只需新建一個(gè)類繼承ThreadLocal即可實(shí)現(xiàn),不同的線程在這個(gè)類中取到各自隔離的變量。
InheritableThreadlocal
InheritableThreadlocal可以在子線程中取得父線程繼承下來(lái)的值。
使用注意:如果子線程取得值的同時(shí),主線程將值進(jìn)行了修改,那么取到的還是舊值。
Lock 的使用
ReenTrantLock可以和synchronized一樣實(shí)現(xiàn)多線程之間的同步互斥,ReenTrantLock類在功能上還更加強(qiáng)大,有嗅探鎖定,多路分支通知等。
使用:
privateLock lock = new ReenTrantLock();try{//加鎖lock.lock();//解鎖lock.unlock();}catch(Exception e){}
ReenTrantLock 結(jié)合 Condition 實(shí)現(xiàn)等待/通知
功能上與synchronized結(jié)合wait/notify一樣,而且更加靈活;
一個(gè)Lock對(duì)象可以創(chuàng)建多個(gè)Condition(即對(duì)象監(jiān)視器)實(shí)例,線程對(duì)象可以注冊(cè)在指定的condition中,從而可以有選擇性的進(jìn)行線程通知,在線程調(diào)度上更加靈活。
而在wait/notify時(shí),被通知的線程是JVM 隨機(jī)選擇的,不如ReenTrantLock 來(lái)得靈活。
synchronized相當(dāng)于整個(gè)lock對(duì)象中只有一個(gè)單一的condition,所有的線程都注冊(cè)在它上面,線程開(kāi)始notify時(shí),需要通知所有的waitting線程,沒(méi)有選擇權(quán),效率不高。
使用之前,必須使用lock.lock()獲取對(duì)象鎖。
private Condition condition = lock.newCondition();try{condition.await();}catch(Exception e){}
其實(shí)使用上:wait()/notify()/notifyAll()相當(dāng)于Condition類
里面的await()/signal()/signalAll()
wait(long timeout)相當(dāng)于await(long time,TimeUnit unit)
1、Spring Boot+Vue項(xiàng)目實(shí)戰(zhàn)
楠哥簡(jiǎn)介
資深 Java 工程師,微信號(hào) nnsouthwind
《Java零基礎(chǔ)實(shí)戰(zhàn)》一書(shū)作者
騰訊課程官方 Java 面試官,今日頭條認(rèn)證大V
GitChat認(rèn)證作者,B站認(rèn)證UP主(楠哥教你學(xué)Java)
致力于幫助萬(wàn)千 Java 學(xué)習(xí)者持續(xù)成長(zhǎng)。



