目錄
前言:
1.常見的鎖策略
1.1樂觀鎖和悲觀鎖
1.2輕量級鎖和重量級鎖
1.3自旋鎖和掛起等待鎖
1.4互斥鎖與讀寫鎖
1.5可重入鎖與不可重入鎖
1.6公平鎖與非公平鎖
2.CAS
2.1什么是CAS
2.2自旋鎖的實(shí)現(xiàn)
2.3原子類
3.synchronized
3.1synchronized的原理以及基本特點(diǎn)
3.2偏向鎖
3.3輕量級鎖
3.4重量級鎖
3.5鎖消除
3.6鎖粗化
4.JUC
4.1JUC中常見到組件
4.1.1callable接口的用法
4.1.2ReentrantLock可重入互斥鎖
4.1.3信號量Semaphore
4.1.4CountDownLatch
5.線程安全的集合類
5.1HashTable
5.2ConcurrentHashMap
5.3ConcurrentHashMap與HashMap的區(qū)別
6.死鎖
6.1什么是死鎖
6.2哲學(xué)家就餐問題
結(jié)束語:
前言:
在上一節(jié)中小編主要是與大家分享了多線程中的線程池的概念以及使用方式,在之前的多線程博客中也個大家介紹了多線程中的一些基礎(chǔ)知識,希望大家下去之后多多練習(xí),鞏固一下,那么從這節(jié)開始小編就開始給大家介紹多線程中的最后一點(diǎn)知識,雖然這些東西不常使用,但是我們還是需要稍微的理解一下的。話不多說我們直接步入正題吧!
1.常見的鎖策略
以下介紹的鎖策略不只是針對java的,別的語言別的工具也會涉及到鎖,也同樣適合用。
1.1樂觀鎖和悲觀鎖
鎖的實(shí)現(xiàn)者會預(yù)測接下來鎖沖突的概率是大還是不大,根據(jù)這個沖突的概率來決定接下來該咋做。這里的鎖的沖突就是鎖競爭,兩個線程在針對一個對象加鎖,另一個就會產(chǎn)生阻塞等待。
這里我們就根據(jù)預(yù)測鎖沖突的概率大小的問題來將鎖分為樂觀鎖和悲觀鎖。
- 樂觀鎖:預(yù)測接下來沖突的概率不大。假設(shè)數(shù)據(jù)一般情況下不會產(chǎn)生并發(fā)沖突,所以在數(shù)據(jù)進(jìn)行提交更新的時候,才會正式的對數(shù)據(jù)是否產(chǎn)生并發(fā)沖突進(jìn)行檢測,如果發(fā)生并發(fā)沖突了,則讓返回用戶錯誤的信息,讓用戶決定如何去做。
- 悲觀鎖:預(yù)測接下來的沖突概率比較大??偸羌僭O(shè)最壞的情況,每次去拿數(shù)據(jù)的時候都認(rèn)為別人會去修改,所以在每次拿數(shù)據(jù)的時候都會鎖上,這樣別人想拿數(shù)據(jù)就會阻塞直到它拿到鎖。
通常來說悲觀鎖做的工作要多一些,但是效率會更低一些,而樂觀鎖做的工作少一些,但是工作的效率會高一些。
1.2輕量級鎖和重量級鎖
- 輕量級鎖:加鎖解鎖過程更快更高效。少量的內(nèi)核態(tài)用戶態(tài)切換,不太容易引起線程調(diào)度。
- 重量級鎖:加鎖解鎖過程更慢更低效。大量的內(nèi)核態(tài)用戶態(tài)切換。很容易引發(fā)線程調(diào)度。
和樂觀和悲觀雖然不是一回事,但是也有一定的重合,一個樂觀鎖很有可能也是一個輕量級鎖,一個悲觀鎖很可能也是一個重量級鎖。
1.3自旋鎖和掛起等待鎖
- 自旋鎖:他是輕量級鎖的一種典型實(shí)現(xiàn)。在鎖競爭中,如果獲取鎖失敗,立即再次嘗試獲取鎖,無限循環(huán),直到獲取到鎖為止,第一次獲取鎖失敗,第二次的嘗試會在極短的時間內(nèi)到來。一旦鎖被其他線程釋放,就能第一時間獲取到鎖。
- 掛起等待鎖:是重量級鎖的一種典型實(shí)現(xiàn)。還是上面的鎖競爭,如果第一次嘗試加鎖失敗,就會進(jìn)入阻塞狀態(tài),需要過很久才會再次調(diào)度。
自旋鎖是一種典型的輕量級鎖的實(shí)現(xiàn)方式。
- 優(yōu)點(diǎn):沒有放棄CPU,不涉及線程阻塞和調(diào)度,一旦被釋放就能第一時間獲取到鎖。
- 缺點(diǎn):如果鎖被其他線程持有的時間比較久,那么就會持續(xù)消耗CPU資源(而掛起等待的時候是不消耗CPU的)。
其中我們之前學(xué)習(xí)的synchronized即是悲觀鎖也是樂觀鎖,即是輕量級鎖也是重量級鎖,輕量級鎖部分基于自旋鎖實(shí)現(xiàn),重量級鎖部分基于掛起等待鎖實(shí)現(xiàn)。
那么究竟什么時候是樂觀鎖,什么時候是悲觀鎖,什么時候是輕量級鎖,什么時候是重量級的鎖,這里synchronized會根據(jù)當(dāng)前鎖競爭的激烈程度自適應(yīng)。如果鎖沖突不激烈,就以輕量級/樂觀鎖的狀態(tài)運(yùn)行,如果鎖沖突激烈,以重量級鎖/悲觀鎖的狀態(tài)運(yùn)行。
1.4互斥鎖與讀寫鎖
- 互斥鎖:像我們之前學(xué)習(xí)的synchronized就是一種互斥鎖,synchronized只有兩個操作,進(jìn)去代碼塊就加鎖,出代碼塊就解鎖。
- 讀寫鎖:就是對讀操作和寫操作分別進(jìn)行加鎖。讀寫鎖有三步:給讀加鎖,給寫加鎖,解鎖。
在讀寫鎖中約定:
- 讀鎖和寫鎖之前不會產(chǎn)生鎖競爭,不會產(chǎn)生阻塞等待。
- 寫鎖和寫鎖之間有所競爭。
- 讀鎖和寫鎖之間也有鎖競爭。
讀寫鎖特別適合于“頻繁讀,不頻繁寫”的場景中。
1.5可重入鎖與不可重入鎖
- 可重入鎖:如果一個鎖,在一個線程中,連續(xù)對該鎖連續(xù)兩次或者是多次加鎖,不發(fā)生死鎖,就叫可重入鎖。
- 不可重入鎖:如果在上述連續(xù)加鎖兩次或者是多次的情況下發(fā)生了死鎖就叫不可重入鎖。
在我們Java里面只要是以Reentrant開頭命名的鎖都是可重入鎖,而且JDK提供所有現(xiàn)成的Lock實(shí)現(xiàn)類,包括synchronized關(guān)鍵字都是可重入鎖。?
1.6公平鎖與非公平鎖
- 公平鎖:遵守“先來后到”,就是公平鎖。
- 非公平鎖:不遵守“先來后到”的就是非公平鎖。
注意:
- 操縱系統(tǒng)內(nèi)部的線程調(diào)度就可以視為是隨機(jī)的,如果不作任何額外的限制,鎖就是非公平的,如果要想實(shí)現(xiàn)公平鎖,就需要依賴額外的數(shù)據(jù)結(jié)構(gòu),來記錄線程們的先后順序。
- 公平鎖和非公平鎖沒有好壞之分,關(guān)鍵還是看適合場景。
- 我們之前學(xué)習(xí)的synchronized就是一個非公平鎖。
2.CAS
2.1什么是CAS
CAS的全稱是:Compare?and?swap,字面意思就是“比較并交換”,一個CAS涉及到以下操作:
我們假設(shè)內(nèi)存中的原數(shù)據(jù)V,舊的預(yù)期值A(chǔ),需要修改的新值B。步驟如下所示:
- 比較A與V是否相等。(比較)
- 如果比較相等,將B寫入V。(交換)
- 返回操作是否成功。
CAS就相當(dāng)于是給我們打開了新世界的大門讓我們不需要加鎖,就能夠保證線程的安全。基于CAS可以實(shí)現(xiàn)很多操作,具體的我們往下看。
2.2自旋鎖的實(shí)現(xiàn)
偽代碼如下所示:
public class SpinLock {
private Thread owner = null;
public void lock(){
//通過CAS看當(dāng)前鎖是否被某個線程持有。
//如果這個鎖已經(jīng)被別的線程持有,那么就自旋等待。
//如果這個鎖沒有被別的線程持有,那么就把owner設(shè)為當(dāng)前嘗試加鎖的線程。
while (!CAS(this.owner,null,Thread.currentThread())){
}
}
public void unlock() {
this.owner = null;
}
}
在上述代碼中如果當(dāng)前owner是null,比較就成功,就把當(dāng)前線程的引用設(shè)置到owner中,加鎖完成,循環(huán)就會結(jié)束,如果比較不成功,意味著owner非空,鎖已經(jīng)有線程持有了,此時CAS就啥也不干,直接返回false,循環(huán)繼續(xù)。此時的循環(huán)就會轉(zhuǎn)的飛快,不停的嘗試詢問這里的鎖是不是釋放了,它的好處就是一旦釋放,就會立即獲取到,壞處就是CPU此時就會處于一種忙等的狀態(tài)。?
2.3原子類
在標(biāo)準(zhǔn)庫中提供了java.util.concurrent.atomic包,里面的都是基于這種方式來實(shí)現(xiàn)的,典型的類就是:AtomicInteger類,其中g(shù)etAndIncrement就相當(dāng)于是++操作,他就能夠保證在++?和 -- 的時候線程是安全的。
圖例演示:
先讀入內(nèi)存中:?
將自增的結(jié)果寫回到CPU中。?
?
另一個先判斷要自增的數(shù)據(jù)是不是和之前讀取到的數(shù)據(jù)一樣,如果不一樣則重新讀入,再自增。
?
接下來我們就用代碼給大家具體來演示一下:
代碼展示:
package Thread;
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadTest02 {
public static void main(String[] args) throws InterruptedException {
AtomicInteger num = new AtomicInteger(0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
//實(shí)現(xiàn)自增
num.getAndIncrement();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
//實(shí)現(xiàn)自增
num.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
//get來獲取到數(shù)值
System.out.println(num.get());
}
}
結(jié)果展示:
?
3.synchronized
3.1synchronized的原理以及基本特點(diǎn)
上面也給大家大概的有提到synchronized的基本特點(diǎn),這里小編再給大家總結(jié)一下:
synchronized的特點(diǎn):
- 即是樂觀鎖又是悲觀鎖。開始時是樂觀鎖,如果鎖沖突頻繁,就轉(zhuǎn)換為悲觀鎖。
- 即是輕量級鎖又是重量級鎖。開始時輕量級鎖,如果鎖被持有時間較長,就轉(zhuǎn)換為重量級鎖。
- 輕量級鎖基于自旋鎖的實(shí)現(xiàn),重量級鎖基于掛起等待鎖的實(shí)現(xiàn)。
- 不是讀寫鎖。
- 是可重入鎖。
- 是非公平鎖。
JVM將synchronized鎖分為無鎖、偏向鎖、輕量級鎖、重量級鎖狀態(tài),會根據(jù)情況進(jìn)行依次升級。
synchronized的關(guān)鍵策略:鎖升級。
加鎖的工作過程如下所示:
下面我們來給大家來分別解釋一下上面的鎖都是什么。??
3.2偏向鎖
第一個嘗試加鎖的線程,優(yōu)先進(jìn)入偏向鎖的狀態(tài)。偏向鎖不是真的“加鎖”,只是給對象頭中做一個“偏向鎖的標(biāo)記”,記錄這個鎖屬于哪個線程。如果后續(xù)沒有其他線程來競爭該鎖,那么就不用子啊進(jìn)行其他操作了(避免了加鎖解鎖的開銷)如果后續(xù)有其他線程來競爭該鎖(剛才已經(jīng)在鎖對象中記錄了當(dāng)前鎖屬于那哪個線程了,很容易識別當(dāng)前申請鎖的線程是不是之前記錄的線程),那就取消原來的偏向鎖狀態(tài),進(jìn)入一般的輕量級鎖狀態(tài)。
偏向鎖的本質(zhì)上相當(dāng)于“延遲加鎖”,能不加鎖就不加鎖,“非必要,不加鎖”,盡量避免不必要的加鎖開銷。但是該做的標(biāo)記還是得做,否則無法區(qū)分何時需要真正加鎖。
3.3輕量級鎖
隨著其他線程進(jìn)入競爭,偏向鎖狀態(tài)被消除,進(jìn)入輕量級鎖狀態(tài)(自適應(yīng)的自旋鎖),此處的輕量級鎖就是通過CAS來實(shí)現(xiàn)。
- 通過CAS檢查并更新一塊內(nèi)存(比如null =>?該線程引用)
- 如果更新成功,則認(rèn)為加鎖成功。
- 如果更新失敗,則認(rèn)為鎖被占用,繼續(xù)自旋式的等待(并不放棄CPU)。
3.4重量級鎖
如果競爭進(jìn)一步激烈,自旋不能快速獲取到鎖狀態(tài),就會膨脹為重量級鎖。
- 執(zhí)行加鎖操作,先進(jìn)入內(nèi)核態(tài)。
- 在內(nèi)核態(tài)判定當(dāng)前鎖是否已經(jīng)被占用。
- 如果該鎖沒有占用,則加鎖成功,并切回用戶態(tài)。
- 如果該鎖被占用,則加鎖失敗,此時線程進(jìn)入鎖的等待的隊(duì)列掛起,等待被操作系統(tǒng)喚醒。
- 經(jīng)歷了一系列的滄海桑田,這個鎖被其他線程釋放了,操作系統(tǒng)也想起了這個掛起的線程,于是喚醒這個線程,嘗試重新獲取鎖。
3.5鎖消除
鎖消除也是“非必要,不加鎖”,他是在編譯階段做的優(yōu)化手段,用來檢測當(dāng)前代碼是否是多線程執(zhí)行/是否有必要加鎖,如果不必要,又已經(jīng)把鎖給寫了,那么就會在編譯階段中將鎖自動去掉。
3.6鎖粗化
首先來解釋一下什么是鎖是粒度,簡單來說就是在synchronized中包含代碼的多少,如果包含的代碼越多,粒度越粗,越少則粒度越細(xì)。一般我們在寫代碼的時候多數(shù)情況下是希望粒度更小一點(diǎn),(串行執(zhí)行的代碼少,并發(fā)執(zhí)行的代碼就多,效率就高)。下面我們可以畫個圖來解釋一下。
?
就比如說在公司中如果你要給領(lǐng)導(dǎo)匯報工作,兩種情況:
情況1:
????????先打電話匯報工作A的進(jìn)展,掛電話。
????????再打電話匯報工作B的進(jìn)展,掛電話。
????????最后打電話匯報工作C的進(jìn)展,掛電話。
情況2:
????????打一個電話匯報工作A、B、C的進(jìn)展,掛電話。?
上述的情況1就相當(dāng)于粒度細(xì)的,情況2相當(dāng)于是粒度粗的情況。
實(shí)際開發(fā)過程中,使用細(xì)粒度鎖,是期望釋放鎖的時候其他線程能使用鎖。但是實(shí)際上可能并沒有其他線程來搶占這個鎖,這種情況下JVM就會自動把鎖粗化,避免頻繁申請釋放鎖。
4.JUC
JUC是java.util.concurrent的縮寫。下面我來看下在里面都有哪些常用到的組件吧!
4.1JUC中常見到組件
4.1.1callable接口的用法
Callable是一個interface,相當(dāng)于把線程封裝了一個“返回值”,方便程序猿借助多線程的方式計算結(jié)果。
他會讓你重寫call方法,在上述中泛型的參數(shù)是啥,call反回的就是啥。?
下面我們來寫一個代碼,創(chuàng)建一個線程,用這個線程來計算:1 + 2 + 3.....+1000。
代碼展示:
package Thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class ThreadTest03 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//這里只是創(chuàng)建了一個任務(wù)
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = 1; i <= 1000; i++) {
sum += i;
}
return sum;
}
};
//創(chuàng)建完任務(wù)之后我們還需要找個人來執(zhí)行這個任務(wù)。
//Thread不能直接傳callable,需要再來包裝一層。
FutureTask<Integer> futureTask = new FutureTask<>(callable);
Thread t = new Thread(futureTask);
t.start();
System.out.println(futureTask.get());
}
}
結(jié)果展示:
在上述代碼中需要我們注意的是FutureTask這個類的使用。
這里面它將我們上述創(chuàng)建的任務(wù)傳遞給futuretask,這里的futuretask?就像是我在吃飯的時候服務(wù)員一般會給我們一張小票,然后等待飯做好之后我們在憑借著這張小票來獲取我們的飯菜,這里也一樣,執(zhí)行完任務(wù)之后,我們就可以憑借著futuretask來獲取我們的任務(wù)的結(jié)果了。這里的futuretask有一個方法是get,通過get方法就可以獲取到我們上述任務(wù)call方法的返回值了。
在之前我們給大家交代了三種創(chuàng)建多線程的方法:繼承自Thread,實(shí)現(xiàn)Runable,基于lambda三種方法,這里我們又學(xué)習(xí)了Callable。
4.1.2ReentrantLock可重入互斥鎖
我們之前學(xué)習(xí)的synchronized關(guān)鍵字是基于代碼塊的方式來控制加鎖解鎖的。ReentrantLock則是提供了lock和unlock獨(dú)立的方法,來進(jìn)行加鎖和解鎖的。雖然我們在大部分情況下使用synchronized就足夠了,但是此處的ReentrantLock也是一個重要的補(bǔ)充。
synchronized與ReentrantLock的區(qū)別:
- synchronized只是加鎖和解鎖,加鎖的時候如果發(fā)現(xiàn)鎖被占用,只能阻塞式等待。ReentrantLock還提供了一個tryLock方法,如果加鎖成功,就沒啥特別的,如果加鎖失敗,是不會阻塞的,直接會返回false,直接把問題拋給程序猿,讓程序猿來靈活的控制。
- synchronized是一個非公平鎖(概率不等,不遵守先來后到的)。而ReentrantLock提供了公平和非公平兩種工作模式(在構(gòu)造方法中,傳入true開啟公平鎖)。
- synchronized搭配wait?notify進(jìn)行等待喚醒,如果多個線程同時wait同一個對象,notify的時候是隨機(jī)喚醒一個,而ReentrantLock則是搭配Condition這個類,這個類也能起到等待通知,可以使得功能更強(qiáng)大。
4.1.3信號量Semaphore
信號量,用來表示“可用資源的個數(shù)”,本質(zhì)上就是一個計數(shù)器。可以把信號量想象成是停車場的展示牌,當(dāng)前有車位100個,表示有100個可用資源。當(dāng)有車開進(jìn)去的時候,就相當(dāng)于申請一個可用資源,可用車位就-1(這個稱為信號量的P操作),當(dāng)有車開出來的時候,就相當(dāng)于釋放了一個可用資源,可用車位就+1(這個稱為信號量的V操作),如果計數(shù)器的值已近為0了,還嘗試申請資源,就會阻塞等待,直到其他線程釋放資源。
Semaphore的PV操作中的加減計數(shù)器操作都是原子的,可以子啊多線程環(huán)境下直接使用。
4.1.4CountDownLatch
CountDownLatch操作就是等待N個任務(wù)執(zhí)行結(jié)束。好像跑步比賽,10位選手依次就位,哨聲吹響之后才能出發(fā),當(dāng)所有選手都通過終點(diǎn)線之后,才算比賽結(jié)束。
5.線程安全的集合類
5.1HashTable
我們之前學(xué)習(xí)過HashMap,他在多線程的環(huán)境下是線程不安全的。這里我們就可以使用HashTable來保證線程的安全性。
不過我們進(jìn)入HashTable的原碼中可以發(fā)現(xiàn)他只是在關(guān)鍵方法上加上了synchronized。如下所示:
?
HashTable是針對整個哈希表進(jìn)行加鎖,任何增刪查改操作,都會被觸發(fā)加鎖,也就都會可能有鎖競爭。
?
所以接下來我們還有另一種解決辦法就是在使用ConcurrentHashMap。
5.2ConcurrentHashMap
相比于HashTable作出了一系列的改進(jìn)和優(yōu)化,我們這里以Java1.8為例。
- 讀操作沒有加鎖,但是使用了volatile保證從內(nèi)存中讀取結(jié)果,只對寫操作進(jìn)行加鎖,加鎖的方式仍然是用synchronized,但是不是整個對象加鎖,而是“鎖桶”(用每一個鏈表的頭結(jié)點(diǎn)作為鎖對象),大大降低了鎖沖突的概率。
- 充分利用CAS特性,比如size屬性通過CAS來更新,避免出現(xiàn)重量級鎖的情況。
- 優(yōu)化了擴(kuò)容的方式:化整為零
- 發(fā)現(xiàn)需要擴(kuò)容的線程,只需要創(chuàng)建一個新的數(shù)組,同時只搬幾個元素過去。
- 擴(kuò)容期間,新老數(shù)據(jù)同時存在
- 后續(xù)每個來操作ConcurrentHashMap的線程,都會參與搬家的過程,每個操作負(fù)責(zé)搬運(yùn)一小部分元素。
- 搬完最后一個元素在把老數(shù)組刪掉。
- 這個期間,插入只往新數(shù)組加。
- 這個期間,查找需要同時查新數(shù)組和老數(shù)組。
ConcurrentHashMap不只是一把鎖,它是將每一個鏈表的頭結(jié)點(diǎn)作為一把鎖,每一次進(jìn)行操作,都是針對對應(yīng)的鏈表的鎖進(jìn)行加鎖,操作不同鏈表就是針對不同的鎖加鎖,就不會有鎖沖突了。
?
5.3ConcurrentHashMap與HashMap的區(qū)別
- HashMap:是線程不安全的,key允許為null。
- HashTable:線程安全,使用synchronized鎖HashTable對象,效率比較低,key不允許為null。
- ConcurrentHashMap:線程安全,使用synchronized鎖每一個鏈表的頭結(jié)點(diǎn),鎖沖突效率低,充分利用CAS機(jī)制,優(yōu)化了擴(kuò)容方式,key不允許為null。
6.死鎖
6.1什么是死鎖
死鎖是這樣的一種情形:多個線程同時被阻塞,它們中的一個或者全部都在等待某個資源被釋放,由于線程被無限期地阻塞,因此程序不可能正常終止。
關(guān)于死鎖的情況:
- 一個線程,一把鎖,可重入鎖沒事,不可重入鎖就會出現(xiàn)死鎖。
- 兩個線程,兩把鎖,即使是可重入鎖也會死鎖。
- N個線程,M把鎖,線程數(shù)量和鎖數(shù)量更多了就更容易出現(xiàn)死鎖了。
6.2哲學(xué)家就餐問題
此時就會出現(xiàn)一個經(jīng)典的問題:哲學(xué)家就餐問題。如下所示:
- 有一個桌子,圍著一圈哲學(xué)家,桌子中間放著一盤面,每個哲學(xué)家兩兩之間,放著一根筷子,每個哲學(xué)家只做兩件事情:思考人生或者是吃面,思考人生的時候就放下筷子,吃面條的時候就會拿起左右兩邊的筷子(先拿起左邊,再拿起右邊)。
?
- 如果哲學(xué)家發(fā)現(xiàn)筷子拿不起來(被別人占用了),就會阻塞等待。
?
- 假設(shè)同一時刻,五個哲學(xué)家同時拿起左手邊的筷子,然后再嘗試拿右手邊的筷子,就會發(fā)現(xiàn)右手邊的筷子都被占用了,由于哲學(xué)家們都不互讓,這個時候就形成了死鎖。
死鎖的四個必要條件:
- 互斥使用:一個線程拿到一把鎖之后,另一個線程不能使用。(鎖的基本特點(diǎn))
- 不可搶占:一個線程拿到鎖,只能自己主動釋放,不能是被其他線程強(qiáng)行占有。(鎖的基本特點(diǎn))
- 請求和保持:“吃著碗里的,想著鍋里的”,即當(dāng)資源請求者在請求其他資源的同時保持原有資源的占有。
- 循環(huán)等待:即存在一個等待隊(duì)列:P1占有P2的資源,P2占有P3的資源,P3占有P1的資源,這樣就形成了一個等待環(huán)路。舉一個例子:“家門鑰匙鎖車?yán)锪耍囪€匙鎖子在家里了”。
注意:上述的死鎖的條件缺一不可?。。?/strong>
死鎖是一個比較嚴(yán)重的bug,那么在實(shí)踐中我們該如何避免出現(xiàn)死鎖呢?
一個簡單那有效的方法就是破解循環(huán)等待的這個條件。我們可以針對鎖進(jìn)行編號,如果需要同時獲取多把鎖,約定加鎖的順序,務(wù)必是先對小的編號加鎖,后對大的編號加鎖。
比如上述哲學(xué)家就餐問題,我們可以對筷子編號,然后讓哲學(xué)家每次的取的時候先取編號較小的,然后在取編號大的。
如下所示:
如果從5號滑稽開始拿筷子,此時5號滑稽先拿起4號筷子,然后再拿起5號筷子就餐,然后4號滑稽拿起3號筷子,3號滑稽拿起2號筷子,2號滑稽拿起1號筷子,此時1號滑稽要想拿起筷子就需要等到2號滑稽就餐完釋放筷子之后在拿起筷子就餐,所以此時1號滑稽就得阻塞等待。此時就不會造成死鎖了。?文章來源:http://www.zghlxwxcb.cn/news/detail-624912.html
結(jié)束語:
這節(jié)中小編主要是與大家分享了多線程中的最后一些知識點(diǎn),可能有些還沒有給大家講解清楚,不影響在后期的學(xué)習(xí)中小編會一一給大家交代的,希望這節(jié)對大家深入了解多線程有一定幫助,想要學(xué)習(xí)的同學(xué)記得關(guān)注小編和小編一起學(xué)習(xí)吧!如果文章中有任何錯誤也歡迎各位大佬及時為小編指點(diǎn)迷津(在此小編先謝過各位大佬啦?。?span toymoban-style="hidden">文章來源地址http://www.zghlxwxcb.cn/news/detail-624912.html
到了這里,關(guān)于多線程(JavaEE初階系列7)的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!