創(chuàng)建、啟動、控制、多線程同步、線程池
進(jìn)程和線程
進(jìn)程:是處于運(yùn)行過程的程序,有一定的獨(dú)立功能,是系統(tǒng)進(jìn)行資源分配和調(diào)度的一個獨(dú)立的單位。特征:獨(dú)立性,動態(tài)性,并發(fā)行。
線程:是進(jìn)程的執(zhí)行單元,是獨(dú)立運(yùn)行的。線程的調(diào)度管理由進(jìn)程本身負(fù)責(zé)完成。
線程
- 創(chuàng)建和啟動
1.1 繼承Thread類創(chuàng)建和啟動
Java中Thread類代表線程,線程對象都必須是Thread類或其子類的實(shí)例。
步驟:定義Thread類的子類,重寫run()方法;run()方法中的代碼就是線程要完成的任務(wù),即線程執(zhí)行體; 創(chuàng)建Thread子類的實(shí)例; 調(diào)用線程對象的start()方法來啟動線程。
多個線程之間無法共享線程類的實(shí)例變量。
1.2. 實(shí)現(xiàn)Runnable接口
是函數(shù)式接口。
步驟:定義Runnable接口的實(shí)現(xiàn)類,重寫的run()方法作為線程執(zhí)行體;創(chuàng)建Runnable實(shí)現(xiàn)類的實(shí)例作為Thread的target來創(chuàng)建Thread對象為真正的線程對象;調(diào)線程對象的start()方法啟動線程。
可以共享線程類的實(shí)例變量。
1.3 使用Callable和Future
通過Callable接口和Future接口就可以在任務(wù)執(zhí)行結(jié)束后獲得任務(wù)執(zhí)行的結(jié)果。
Callable是函數(shù)式接口,call()方法作為線程執(zhí)行體。call()方法有返回值,call()方法可以聲明拋出異常。Callable接口不是Runnable接口的子接口,Callable對象不能直接作為Thread的target。
Future接口代表Callable接口里call()方法的返回值。FutureTask為Future接口和Runnable接口的實(shí)現(xiàn)類,F(xiàn)utureTask實(shí)現(xiàn)類可以作為Thread類的target。
步驟:實(shí)現(xiàn)Callable接口的call()方法并創(chuàng)建Callable接口的對象;使用FutureTask類包裝Callable對象;創(chuàng)建FutureTask的對象作為Thread對象的target。
Future接口的get()方法:會導(dǎo)致程序阻塞,子線程結(jié)束后才會得到返回值。
- 線程的生命周期
共有5種狀態(tài)。
新建:用new關(guān)鍵字創(chuàng)建了一個線程后,線程就處于新建狀態(tài)。JVM會分配空間初始化成員變量的值,但不會表現(xiàn)出任何線程的動態(tài)特征,也不會執(zhí)行線程的線程執(zhí)行體。
就緒:調(diào)用start()方法后就處于就緒狀態(tài),JVM會創(chuàng)建方法調(diào)用棧和程序計數(shù)器。
運(yùn)行:處于就緒狀態(tài)的線程獲得cpu后處于運(yùn)行狀態(tài)。
- 就緒和運(yùn)行狀態(tài)的轉(zhuǎn)換不受程序的控制,由系統(tǒng)線程調(diào)度決定。
yield()方法會讓運(yùn)行狀態(tài)的線程轉(zhuǎn)入就緒狀態(tài)。
阻塞:調(diào)用了sleep()方法;調(diào)用了阻塞式IO方法;在等某個通知(notify);調(diào)用了suspend()方法將線程掛起(容易導(dǎo)致死鎖)。
- 阻塞后會重新進(jìn)入就緒狀態(tài)。
調(diào)用resume()方法恢復(fù)被掛起的線程。
死亡:run()/call()方法執(zhí)行結(jié)束,線程正常結(jié)束;拋出一個未捕獲的Exception/Error;stop()方法結(jié)束該線程(容易導(dǎo)致死鎖)
- 主線程結(jié)束后其他線程不受影響,并不會隨之結(jié)束。
判斷線程是否已經(jīng)死亡:isAlive()方法。線程處于就緒、運(yùn)行、阻塞時返回true,處于新建、死亡時返回false。
- 控制線程
控制線程的執(zhí)行。
3.1 join線程
A線程中調(diào)用了B線程的join()方法,A線程會等待B線程完成才會執(zhí)行。
3.2 后臺線程
在后臺運(yùn)行,為其他的線程提供服務(wù)。別名:守護(hù)線程,精靈線程。
所有的前臺線程都死亡,后臺線程會自動死亡。
使用Thread對象的setDaemon(true)方法將指定的線程設(shè)置為后臺線程。
setDaemon(true)方法必須在start()方法之前調(diào)用。
3.3 線程睡眠:sleep
當(dāng)前執(zhí)行的線程暫停一段時間,進(jìn)入阻塞狀態(tài)。
調(diào)用Thread類的靜態(tài)sleep()方法實(shí)現(xiàn)。
線程調(diào)用sleep()方法后,在該線程的睡眠時間內(nèi),即使系統(tǒng)種沒有其他的可以執(zhí)行的線程,該線程也不會執(zhí)行。
3.4 線程讓步:yield
是Thread類的靜態(tài)方法,會讓執(zhí)行的線程暫停進(jìn)入就緒狀態(tài)。
會讓當(dāng)前線程暫停,系統(tǒng)的線程調(diào)度器重新調(diào)度一次。
優(yōu)先級與當(dāng)前線程相同,或者優(yōu)先級高于當(dāng)前線程的處于就緒狀態(tài)的線程會獲得執(zhí)行的機(jī)會。
3.5 改變線程的優(yōu)先級
優(yōu)先級高的線程獲得多的執(zhí)行機(jī)會,優(yōu)先級低的線程獲得少的執(zhí)行機(jī)會。
- 線程同步
4.1 線程安全問題
使用同步監(jiān)視器來解決,即通過使用同步代碼塊。
4.2 使用synchronized進(jìn)行同步
- 同步代碼塊
線程開始執(zhí)行同步代碼塊之前要先獲得對同步監(jiān)視器的鎖定。任何時刻只有一個線程可以獲得對同步監(jiān)視器的鎖定,在執(zhí)行完同步代碼塊之后,線程就會釋放對同步監(jiān)視器的鎖定。
可能被并發(fā)訪問的共享資源的對象做同步監(jiān)視器,同步監(jiān)視器控制著對象的synchronized代碼。
監(jiān)視器作用:確保同一時間只有一個線程可以訪問共享資源。
- Java中監(jiān)視器的實(shí)現(xiàn)
JVM中,每個對象關(guān)聯(lián)監(jiān)視器,即每個對象都是一個臨界區(qū)。實(shí)現(xiàn)監(jiān)視器的互斥功能:每個對象關(guān)聯(lián)著一個鎖,即操作系統(tǒng)中的信號量?;コ馐且粋€二進(jìn)制的信號量。Java內(nèi)置鎖也稱為互斥鎖,即鎖實(shí)際上是一種互斥機(jī)制。
信號量:分為二進(jìn)制信號量(互斥鎖)和計數(shù)信號量。用來解決臨界區(qū)及進(jìn)程同步問題。存在P(wait())操作與V(signal)操作兩種操作。
同步監(jiān)視器和鎖:這兩者的關(guān)系類似于JVM中方法區(qū)和永久代的關(guān)系。
- 同步方法
就是使用synchronized關(guān)鍵字修飾某個方法。
修飾實(shí)例方法:不需要顯式指定同步監(jiān)視器,同步監(jiān)視器是this,即調(diào)用該方法的對象。
并不能使調(diào)用該方法的多個對象在執(zhí)行順序上互斥。
敲重點(diǎn):synchronized修飾this代碼塊以及非static的方法時,即當(dāng)鎖為對象鎖時,只能防止多個線程同時執(zhí)行同一個對象的同步代碼段。
線程安全的類:Vector,Hashtable,StringBuffer都是線程安全的類。該類的對象可以被多個線程安全的訪問,每個線程調(diào)用該對象的任何方法之后都會得到正確結(jié)果。只對線程安全類的會改變競爭資源的方法進(jìn)行同步。
- 同步靜態(tài)方法
就是使用synchronized修飾靜態(tài)方法,控制該類的所有實(shí)例的并發(fā)訪問,限制多線程中該類的所有實(shí)例同時訪問該類所對應(yīng)的代碼塊。
修飾靜態(tài)方法:同步監(jiān)視器就是當(dāng)前類的class對象鎖。class對象鎖可以控制對靜態(tài)成員的并發(fā)操作。
class對象鎖相當(dāng)于該類的一個全局鎖,無論實(shí)例多少個對象,線程都共享該鎖。
4.3 釋放同步監(jiān)視器的鎖定
無法顯式釋放對同步監(jiān)視器的鎖定。
會釋放:在同步方法或者同步代碼塊中出現(xiàn)未處理的Error或者Exception導(dǎo)致異常結(jié)束時,會釋放。執(zhí)行了同步監(jiān)視器的wait()方法后,當(dāng)前線程暫停并釋放同步監(jiān)視器。
不會釋放:調(diào)用sleep()、yield()方法時;
synchronized的缺陷:當(dāng)獲取鎖的線程因?yàn)閕o等原因阻塞而不能釋放鎖,其他的線程只能一直等待,影響程序效率。synchronized不可以知道線程有沒有成功獲取到鎖。
4.4 同步鎖
Lock同步鎖可以讓等待鎖的線程響應(yīng)中斷,但synchronized會讓等待的線程一直等待下去,不能夠響應(yīng)中斷。
通過同步鎖可以不讓等待的線程一直無限期的等待下去;通過同步鎖可以知道線程有沒有成功獲取到鎖。
通過顯式定義同步鎖對象--> Lock對象實(shí)現(xiàn)同步。
通過同步鎖實(shí)現(xiàn)同步時,必須要用戶手動去釋放鎖,如果沒有手動釋放,會導(dǎo)致死鎖。
使用Lock時顯式使用Lock對象作為同步鎖,使用synchronized時隱式使用當(dāng)前對象作為同步監(jiān)視器。
Lock
Lock是一個接口,lock(),tryLock():用來獲取鎖,unlock():用來釋放鎖。
lock()方法:鎖已經(jīng)被其他線程獲取,則進(jìn)行等待。
在try{}catch{}塊中使用Lock,釋放鎖的操作在finally中進(jìn)行,保證鎖一定會釋放,防止死鎖的發(fā)生。
以這種形式使用:
Lock lock = ...;
lock.lock();
try{
//處理任務(wù)
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
tryLock()方法:在獲取到鎖時返回true,獲取不到時返回false,這個方法在拿不到鎖時不會一直在等待會立即返回。
tryLock()獲取鎖:
Lock lock = ...;
if(lock.tryLock()) {
try{
//處理任務(wù)
}catch(Exception ex){
}finally{
lock.unlock(); //釋放鎖
}
}else {
//如果不能獲取鎖,則直接做其他事情
}
lockInterruptibly()方法:使用這個方法獲得鎖時,線程在等待獲取鎖時,能夠響應(yīng)中斷,可以中斷線程的等待狀態(tài)。
要在try塊中或者調(diào)用lockInterruptibly()的方法外拋出InterruptedException異常。
lockInterruptibly()方法可以中斷線程的等待狀態(tài)的原理類似于線程調(diào)用wait()后進(jìn)行中斷會重新喚醒線程的原理。
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}
ReentrantLock
可重入鎖,是唯一一個實(shí)現(xiàn)了Lock接口的類。
獨(dú)占鎖:同一個時間點(diǎn)只能被一個線程訪問。
輪詢鎖和定時鎖:通過tryLock()方法實(shí)現(xiàn)的,tryLock()方法在獲取鎖時如果鎖已經(jīng)被其他線程所占有,則立即返回,不會一直等待阻塞下去,同時在返回后會釋放自己所持有的鎖,可以根據(jù)返回的結(jié)果進(jìn)行重試或者取消,避免死鎖發(fā)生。
公平鎖:按照請求鎖的順序來獲取鎖。
非公平鎖的性能要高于公平鎖。
可中斷鎖:使用lockInterruptibly()方法可以中斷線程的等待狀態(tài)。
ReentrantLock和synchronized都是可重入鎖。
可重入性:表明了鎖的分配機(jī)制,是基于線程的分配,不是基于方法調(diào)用的分配。
ReadWriteLock
是一個接口,只有兩個方法,readLock():用來獲取讀鎖,writeLock():用來獲取寫鎖。將文件的讀與寫操作分開,分成兩個鎖分配給線程。使得多個線程的讀操作不沖突。是一個互斥鎖:讀鎖和寫鎖相互互斥,但是多個讀鎖之間不互斥。
具有重入性,讀線程插隊,寫鎖降級到讀鎖,讀鎖升級到寫鎖,釋放優(yōu)先。
ReentrantReadWriteLock
實(shí)習(xí)了ReadWriteLock接口。
4.5 死鎖
一個線程等待另一個線程持有的鎖時,另一個線程也在等待該線程所持有的鎖時,兩個線程都會處于阻塞狀態(tài),會出現(xiàn)死鎖。即兩個線程相互等待對方釋放同步監(jiān)視器時會發(fā)生死鎖。
線程通信
- 使用wait()、notify()、notifyAll()進(jìn)行通信
與對象監(jiān)視器配合完成線程間通信。用到了兩種機(jī)制:互斥鎖機(jī)制和內(nèi)置的條件變量機(jī)制。
synchronized修飾的方法中同步監(jiān)視器是this,可以直接調(diào)用這三個方法。
synchronized修飾的同步代碼塊中必須使用synchronized括號里面的對象來調(diào)用這三個方法。
wait()、notify()、notifyAll()是Object類的final方法,無法被重寫。調(diào)用這三個方法時當(dāng)前線程必須獲得對調(diào)用這三個方法的對象的同步監(jiān)視器的鎖定。
wait():使當(dāng)前線程阻塞,直到接到通知或者當(dāng)前線程被中斷為止。
調(diào)用wait()方法的當(dāng)前線程會釋放對同步監(jiān)視器的鎖定,讓出cpu,進(jìn)入等待狀態(tài),線程的生命周期狀態(tài)被調(diào)整為WAITTING。
直到其他線程調(diào)用該同步監(jiān)視器的notify()或notifyAll()方法來喚醒該線程,該線程的生命周期狀態(tài)會變?yōu)镽UNNABLE。
被喚醒的處于RUNNABLE狀態(tài)的線程會與其他處于RUNNABLE狀態(tài)的線程競爭鎖,如果被喚醒的線程競爭到鎖,該線程會從wait()方法處返回,然后繼續(xù)往下執(zhí)行。
notify():喚醒在此同步監(jiān)視器上等待的單個線程。
只是喚醒沉睡的線程,直到執(zhí)行完synchronized代碼塊或是遇到wait()方法,才會釋放鎖。
只有當(dāng)前線程放棄對同步監(jiān)視器的鎖定后,才會執(zhí)行被喚醒的線程。
在這個同步監(jiān)視器上等待的線程很多的情況下會選擇一個任意的線程去喚醒。
notifyAll():喚醒在此同步監(jiān)視器上的所有的等待的線程。
wait(long)和wait(long,int):對前一個方法,如果等待線程在接到通知或被中斷之前,已經(jīng)超過了wait(long)中規(guī)定的時間,該等待線程就會自動釋放自己被喚醒,喚醒后回去競爭鎖。
wait(long)和wait(long,int)返回時不會返回任何相關(guān)的信息,所以不能確定是因?yàn)榻拥搅送ㄖ€是因?yàn)槌瑫r而被喚醒。可以通過設(shè)置標(biāo)志位來進(jìn)行判斷。
鎖池和等待池
鎖池(入口集):一個對象鎖已經(jīng)被一個線程所擁有,其他線程想要得到該對象鎖時,就會進(jìn)入該對象的鎖池中。
進(jìn)入鎖池的線程的生命周期狀態(tài)被調(diào)整為BLOCKED。
等待池(等待集):一個線程調(diào)用了它所擁有的對象鎖的對象的wait()方法,這個線程會進(jìn)入阻塞,并釋放該對象鎖,進(jìn)入該對象的等待池。
進(jìn)入等待池的線程的生命周期狀態(tài)被調(diào)整為WAITTING。
等待池中的線程不會去競爭對象的鎖,即不會參與線程調(diào)度。
敲重點(diǎn):notify()和notifyAll()的區(qū)別
notifyAll()方法:調(diào)用后,將全部的線程由等待池移到鎖池,參與鎖的競爭,競爭到鎖的線程會繼續(xù)執(zhí)行,沒有競爭到鎖的線程留在鎖池等待鎖被釋放后再次參與競爭。
notify()方法:會將等待池中的一個隨機(jī)線程移到鎖池。
wait()、notify()、notifyAll()方法的底層原理
wait():調(diào)用wait()時,會將synchroniezd鎖定的鎖釋放掉,再將當(dāng)前的線程阻塞再一個內(nèi)置的條件上,這個內(nèi)置的條件只能被notify()/notifyAll()改變后該當(dāng)前線程才可以重新去競爭鎖。
notify()/notifyAll():起到一個內(nèi)置條件變量的作用。調(diào)用notify()/notifyAll()后,處于wait()狀態(tài)中的線程等待的內(nèi)置條件會被滿足。
wait()線程所等待的內(nèi)置條件被滿足后,wait()的線程還要獲得鎖,它會等notify()/notifyAll()的線程執(zhí)行完同步代碼塊并將鎖釋放掉后,去競爭被釋放的鎖。
總結(jié):一個線程使用wait()后可以被喚醒的情況
a. 其他線程調(diào)用Object.notify(),并且當(dāng)前線程T正好是被選中喚醒的。
b. 其他線程調(diào)用Object.notifyAll()。
c. 其他線程中斷T。
d. 指定的等待時間超時。
- 使用Condition進(jìn)行線程通信
Condition是個接口,Condition與Lock配合完成線程間通信。
通過lock.new Condition()來創(chuàng)建Condition對象,實(shí)際上new出的是ConditionObject對象。ConditionObiect類是AQS的一個內(nèi)部類,是Condition在Java并發(fā)中的具體實(shí)現(xiàn)。
(ConditionObject和AQS?)
Condition與synchronized在功能特性上的不同點(diǎn)
Condition可以支持不響應(yīng)中斷,而Object的方式不支持。?wait()中斷
Condition可以支持多個等待隊列(new多個Condition對象),Object的方式只支持一個。
Condition可以支持超時時間的設(shè)置,Object不支持。
Condition中支持的方法
類似于Object,Condition中有:
void await() throws InterruptedException:使當(dāng)前線程阻塞。
當(dāng)其他線程調(diào)用Condition的signal()或signalAll()方法或中斷當(dāng)前線程時會喚醒當(dāng)前線程,并且當(dāng)前線程獲取到Lock鎖時會從await()方法處返回。
當(dāng)前線程在等待狀態(tài)中被中斷則會拋出異常。
long await(long):使當(dāng)前線程阻塞。
直到當(dāng)前線程接到通知,中斷或超時時會退出阻塞狀態(tài)。
boolean await(Date) throws InterruptedException:類似于上一個。
void awaitUninterruptibly():使當(dāng)前線程阻塞,直到被通知,對中斷不做響應(yīng)。
void signal():喚醒在Condition對象上等待的線程。
會將該線程從等待隊列移到同步隊列(從等待池移到鎖池),在同步隊列中可以競爭到Lock鎖,則會從等待方法處返回。
void signalAll():喚醒所有的在Condition對象上等待的線程。
- Condition的await()和signal()方法必須在lock.lock()和lock.unlock()方法之間才可以使用。
Condition實(shí)現(xiàn)原理分析(ConditionObject的具體實(shí)現(xiàn))
以ConditionObject的等待隊列、等待、通知三方面分析ConditionObject的具體實(shí)現(xiàn)。
- 等待隊列
ConditionObject的等待隊列是一個單向的FIFO隊列,隊列上的每個節(jié)點(diǎn)都是等待在Condition對象上的線程的引用。
調(diào)用Condition.await()方法的線程會釋放鎖,構(gòu)造相應(yīng)的Node節(jié)點(diǎn)進(jìn)入Condition內(nèi)部維護(hù)的等待隊列中等待,線程的狀態(tài)變?yōu)閃AITING。節(jié)點(diǎn)的定義復(fù)用AQS的Node定義。
ConditionObject的等待隊列的相關(guān)定義和方法
public class ConditionObject implements Condition, java.io.Serializable {
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
......
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//將當(dāng)前線程包裝成Node
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//尾插入
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
//更新latWaiter
lastWaiter = node;
return node;
}
......
}
等待隊列的基本結(jié)構(gòu)如下圖:

ConditionObject包含等待隊列的首節(jié)點(diǎn)firstWaiter和尾節(jié)點(diǎn)lastWaiter,通過等待隊列的頭尾指針來管理等待隊列。
線程調(diào)用await()方法時,會調(diào)用addConditionWaiter()方法將線程加入等待隊列中。
插入新節(jié)點(diǎn)采用尾插法,將原有節(jié)點(diǎn)的nextWaiter指向新節(jié)點(diǎn),并更新尾節(jié)點(diǎn)lastWaiter指向新插入的新節(jié)點(diǎn)。
更新節(jié)點(diǎn)并沒有像AQS更新同步隊列使用CAS是因?yàn)檎{(diào)用await()方法的線程一定是獲得了鎖的線程,鎖保證了操作線程的線程安全。
一個Lock同步鎖可以有多個等待隊列 --> 多次調(diào)用lock.new Condition()方法創(chuàng)建多個Condition對象。
對象的對象監(jiān)視器上只能有一個同步隊列和一個等待隊列。
并發(fā)包中的Lock有一個同步隊列和多個等待隊列。
等待隊列不帶頭節(jié)點(diǎn),同步隊列帶頭節(jié)點(diǎn)(AQS?)
- 等待過程
Condition的await()系列的方法會使當(dāng)前獲取loak鎖的線程進(jìn)入到等待隊列,當(dāng)前線程可以從await()方法返回的話就一定會獲得相關(guān)的lock鎖。
在await()代碼過程主要分析3點(diǎn):怎么將當(dāng)前線程加入到等待隊列中去的?釋放鎖的過程?怎么從await()方法退出返回?
ConditionObject的await()方法
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//將當(dāng)前線程包裝成Node,尾插入等待隊列
Node node = addConditionWaiter();
//釋放當(dāng)前線程所占用的鎖 ,釋放過程中會喚醒同步隊列中的下一個節(jié)點(diǎn)
long savedState = fullyRelease(node);
int interruptMode = 0;
//當(dāng)前線程受到喚醒加入到同步隊列時退出while循環(huán)
while (!isOnSyncQueue(node)) {
//當(dāng)前線程進(jìn)入等待狀態(tài)
LockSupport.park(this);
// 當(dāng)前線程受到中斷時退出while循環(huán)
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//自旋等待獲取到lock鎖
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//處理中斷的情況
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
具體過程如下:
a. 調(diào)用addConditionWaiter()方法將當(dāng)前線程加入到等待隊列。
b. 在fullyRelease()方法中進(jìn)行同步鎖的釋放。
在fullyRelease()方法中調(diào)用release()方法釋放同步鎖并喚醒在等待隊列中的頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)引用的線程。成功釋放就正常返回,失敗則會拋出異常。
c. 在while()循環(huán)中線程進(jìn)入等待狀態(tài),在線程被中斷或線程在被喚醒后被移到同步隊列時退出while循環(huán),接著不斷自旋試著去獲取到同步鎖。
獲取到同步鎖時,線程會從await()方法返回。
不響應(yīng)中斷
不相應(yīng)中斷調(diào)用了Condition的awaitUninterruptibly()方法,在這個方法中減少了對中斷的處理,也省略了拋出被中斷的異常。
- 通知過程
Condition的signal()方法會將等待隊列中的等待時間最長節(jié)點(diǎn)即等待隊列的頭節(jié)點(diǎn)移動到同步隊列,使該節(jié)點(diǎn)可以去競爭鎖。
ConditionObject的signal()方法
public final void signal() {
//先檢測當(dāng)前線程是否已經(jīng)獲取到lock鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//獲取等待隊列的第一個節(jié)點(diǎn)
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
之后會將等待隊列的頭節(jié)點(diǎn)從等待隊列中移除,再將頭節(jié)點(diǎn)插入到同步隊列中,最后使用LockSypport.unpark()方法喚醒該節(jié)點(diǎn)的線程。
signalAll()會調(diào)用doSignalAll()方法將等待隊列的每一個節(jié)點(diǎn)都移入到同步隊列中。
- 使用阻塞隊列(BlockingQueue)
是一個接口。
使用阻塞隊列的優(yōu)點(diǎn):不需要我們自己去處理什么時候阻塞線程和喚醒線程,阻塞隊列會幫我們?nèi)ヌ幚怼?br>
阻塞隊列有一個完整隊列所具有的基本功能。在多線程環(huán)境下,阻塞隊列自動管理了多線程間的自動等待和喚醒功能。
多線程中,通過隊列實(shí)現(xiàn)數(shù)據(jù)共享。通過線程安全的隊列類,解決了多線程中的高效安全傳輸數(shù)據(jù)的問題。
阻塞隊列中線程的阻塞:在某些情況下會阻塞,條件滿足后被阻塞的線程會被自動喚醒。
常見阻塞場景:1. 在隊列為空時,消費(fèi)者線程會被阻塞,直到該阻塞隊列為非空。2. 在隊列滿時,生產(chǎn)者線程會被阻塞,直到該阻塞隊列變?yōu)榉菨M。
BlockingQueue阻塞隊列的方法
分為兩大類方法:放入數(shù)據(jù)和獲取數(shù)據(jù)。
- 放入數(shù)據(jù)
offer(Object):將數(shù)據(jù)加入到阻塞隊列,可以放入返回true,否則返回false。該方法不阻塞當(dāng)前執(zhí)行方法的線程。
offer(Object,long):在等待時間內(nèi)不能往等待隊列中加入數(shù)據(jù)就返回false。
put(Object):如果阻塞隊列沒有空間加入數(shù)據(jù),則調(diào)用此方法的線程被阻塞直到阻塞隊列里面有空間再繼續(xù)加入。
- 獲取數(shù)據(jù)
poll(time):取走阻塞隊列中的排在首位的對象。不能立即取出時會等待time參數(shù)規(guī)定的時間,超過time時間后還取不到會返回null。
take():取走阻塞隊列里排在首位的數(shù)據(jù)。在阻塞隊列為null時,會使當(dāng)前線程阻塞直到阻塞隊列有新的數(shù)據(jù)被加入。
drain():可以指定獲取數(shù)據(jù)的個數(shù)的一次性從阻塞隊列中獲取所有可用的數(shù)據(jù)對象。不需要分批次的加鎖或釋放鎖。
常見的阻塞隊列
- ArrayBlockingQueue
基于數(shù)組的阻塞隊列的實(shí)現(xiàn)。
在其內(nèi)部維護(hù)了一個定長數(shù)組,來緩存阻塞隊列中的數(shù)據(jù)對象。
按照FIFO先進(jìn)先出排序元素。
內(nèi)部的兩個整型變量分別標(biāo)識著隊列頭部和尾部在數(shù)組中的位置。
ArrayBlockingQueue的生產(chǎn)者和消費(fèi)者線程公用一個鎖對象,所以生產(chǎn)者和消費(fèi)者線程無法并行運(yùn)行。
創(chuàng)建ArrayBlockingQueue時,可以控制對象的內(nèi)部鎖是否采用公平鎖,默認(rèn)采用非公平鎖。
- LinkedBlockingQueue
基于鏈表的阻塞隊列。
按照FIFO先進(jìn)先出排序元素。
在其內(nèi)部維護(hù)了一個由鏈表構(gòu)成的數(shù)據(jù)緩沖隊列,來緩存阻塞隊列中的數(shù)據(jù)對象。
可以通過LinkedBlockingQueue的構(gòu)造函數(shù)來指定阻塞隊列的最大緩存容量。
在構(gòu)造LinkedBlockingQueue對象時,沒有指定容量大小的時會默認(rèn)使用一個類似無限大小的容量(Integer.MAX_VALUE)。這樣可能會產(chǎn)生當(dāng)生產(chǎn)者線程生產(chǎn)的速度大于消費(fèi)者線程消費(fèi)的速度時會使得系統(tǒng)內(nèi)存消耗嚴(yán)重,可能會造成內(nèi)存泄漏等問題。
每個添加到LinkedBlockingQueue隊列中的數(shù)據(jù)都會被封裝成Node節(jié)點(diǎn)。
生產(chǎn)者線程和消費(fèi)者線程分別采用了獨(dú)立的鎖來控制數(shù)據(jù)同步,生產(chǎn)者和消費(fèi)者可以并行的操作阻塞隊列的數(shù)據(jù)。
生產(chǎn)者線程往阻塞隊列中放入數(shù)據(jù)時,阻塞隊列行將數(shù)據(jù)緩存在內(nèi)部,生產(chǎn)者線程會立即返回。
當(dāng)阻塞隊列達(dá)到最大的緩存容量時,會阻塞生產(chǎn)者線程,消費(fèi)者線程消費(fèi)掉阻塞隊列中的一份數(shù)據(jù)時生產(chǎn)者線程會被喚醒。
消費(fèi)者線程的原理類似于生產(chǎn)者線程。
在插入或刪除元素時和ArrayBlockingQueue的不同之處
ArrayBlockingQueue在插入或刪除時不會產(chǎn)生或銷毀任何額外的對象實(shí)例。
LinkedBlockingQueue在插入或刪除時會生成一個額外的Node對象。
深入ArrayBlockingQueue和LinkedBlockingQueue的異同
相同
當(dāng)隊列為空時消費(fèi)者線程被阻塞;當(dāng)隊列為滿時,生產(chǎn)者線程被阻塞。都是可阻塞的隊列。
都實(shí)現(xiàn)了BlockingQueue接口。內(nèi)部都是使用了ReentrantLock和Condition來保證生產(chǎn)和消費(fèi)的同步。
使用Condition的await()和signal()方法來進(jìn)行線程通信。
不同
a. 鎖機(jī)制不同
LinkedBlockingQueue中鎖是分離的,生產(chǎn)者線程和消費(fèi)者線程使用的不同的鎖。
ArrayBlockingQueue中生產(chǎn)者和消費(fèi)者線程使用的是同一把鎖。
b. 兩者的底層實(shí)現(xiàn)機(jī)制不同
LinkedBlockingQueue內(nèi)部維護(hù)一個鏈表結(jié)構(gòu)。在插入或刪除時會生成一個額外的Node對象,可能在長時間高并發(fā)處理大量數(shù)據(jù)時會對GC產(chǎn)生較大的影響。
ArrayBlockingQueue內(nèi)部維護(hù)了一個循環(huán)隊列數(shù)組。在插入或刪除元素時不會產(chǎn)生或銷毀任何額外的對象實(shí)例。
c. 構(gòu)造時候的區(qū)別
LinkedBlockingQueue有默認(rèn)的容量大小,也可以傳入指定的容量大小。
ArrayBlockingQueue在初始化時必須指定大小。
- DelayQueue
是一個沒有大小限制的阻塞隊列,所以進(jìn)行插入操作的生產(chǎn)者線程永遠(yuǎn)不會被阻塞,獲取數(shù)據(jù)的消費(fèi)者線程會被阻塞。
這個阻塞隊列中的元素只有當(dāng)其指定的延遲時間到了,才可以從隊列中獲取到該元素。
使用場景:用該阻塞隊列來管理一個超時未響應(yīng)的連接隊列。
- PriorityBlockingQueue
基于優(yōu)先級的阻塞隊列。
通過在構(gòu)造函數(shù)中傳入Compator對象來決定優(yōu)先級的判斷。內(nèi)部控制線程采用的鎖是公平鎖。
該阻塞隊列不會阻塞生產(chǎn)者線程,在沒有可以取出的數(shù)據(jù)時會阻塞消費(fèi)者線程。
使用時注意不要讓生產(chǎn)者線程生產(chǎn)的速度大于消費(fèi)者線程消費(fèi)的速度,否則會產(chǎn)生嚴(yán)重消耗堆內(nèi)存。
- SynchronousQueue
是一種無緩沖的等待隊列。
生產(chǎn)者消費(fèi)者模型
在系統(tǒng)中存在生產(chǎn)者和消費(fèi)者,他們通過內(nèi)存緩沖區(qū)進(jìn)行通信。
實(shí)現(xiàn):生產(chǎn)者是一些線程,消費(fèi)者是一些線程,內(nèi)存緩沖區(qū)使用List數(shù)組隊列,然后處理多線程之間的協(xié)作。
注意:內(nèi)存緩沖區(qū)為空時消費(fèi)者必須等待;內(nèi)存緩沖區(qū)為滿時生產(chǎn)者必須等待。
synchronized鎖住的是括號里的對象,不是代碼。?
同步監(jiān)視器為類鎖時,實(shí)現(xiàn)了全局鎖的效果,只要這個synchronized括號中的對象為該類的對象時,都會實(shí)現(xiàn)同步鎖定的效果。synchronized鎖住的是類的Class對象。
在同步方法或者同步代碼塊中出現(xiàn)未處理的Error或者Exception?
執(zhí)行了同步監(jiān)視器的wait()方法后,當(dāng)前線程暫停并釋放同步監(jiān)視器?
同步監(jiān)視器
同步監(jiān)視器和鎖
同步監(jiān)視器和synchronized
synchronized鎖的是什么?
synchronized的實(shí)現(xiàn)原理?
synchronized修飾實(shí)例方法時
的同步監(jiān)視器?
線程安全的類?
volatile
同步方法和同步代碼塊
Lock和異常
Lock鎖住的對象?
可重入鎖
wait(),notify(),notifyAll()和生產(chǎn)者消費(fèi)者
怎么中斷
wait(long timeout)超時之后線程處于鎖池還是等待池。
wait()和中斷
為什么wait()后再進(jìn)行中斷線程就會活過來?
AQS
AQS更新同步隊列使用CAS?
等待隊列不帶頭節(jié)點(diǎn),同步隊列帶頭節(jié)點(diǎn)(AQS?)
AQS的模板方法release方法釋放AQS的同步狀態(tài)并且喚醒在同步隊列中頭結(jié)點(diǎn)的后繼節(jié)點(diǎn)引用的線程?
在await()中釋放鎖的過程?
Condition的同步隊列
Condition的同步隊列和Object的同步隊列
怎么實(shí)現(xiàn)無鎖同步?
Concurrent包原理
Concurrent原理
juc包
park和unpark()?
transient
雙重檢查上鎖實(shí)現(xiàn)的單例模式。
Java單例模式中雙重檢查鎖的問題
https://www.cnblogs.com/wanly3643/p/3992186.html
https://blog.csdn.net/qpc908694753/article/details/61413693
https://hacpai.com/article/1488015279637
https://blog.csdn.net/qq_38663729/article/details/78232648
看了異常后:Lock的interruptLock()的拋出異常
使用wait()后被中斷喚醒拋出異常
Condition的不響應(yīng)中斷和響應(yīng)中斷 拋出的異常