目錄
前言
一.synchronized
1.1概念?
1.2Synchronized是什么鎖?
1.3Synchronized加鎖工作過程
1.4其他優(yōu)化操作
二.死鎖
2.1什么是死鎖
2.2死鎖的幾個經(jīng)典場景
2.3死鎖產(chǎn)生的條件
2.4如何解決死鎖
??個人主頁:tq02的博客_CSDN博客-C語言,Java,Java數(shù)據(jù)結(jié)構(gòu)領域博主
?? 本文由 tq02 原創(chuàng),首發(fā)于 CSDN??
???本章講解內(nèi)容:多線程的策略鎖、CAS和JUC
??多線程學習專欄:多線程學習專欄
??其他學習專欄:??C語言?? ? ? ??JavaSE?? ? ??MySQL基礎?
前言
? ? ? ? 在多線程的講解當中,我們可以知道synchronized是加鎖操作,讓兩個線程發(fā)生互斥效果,在代碼中使用synchronized關鍵字來實現(xiàn)鎖的獲取和釋放。如果是剛剛接觸多線程的人,我希望你可以從第一章多線程開始學習:http://t.csdn.cn/0vEhY
一.synchronized
1.1概念?
????????Synchronized是Java中內(nèi)置的鎖機制,用于實現(xiàn)線程同步。它可以通過在代碼中使用synchronized關鍵字來實現(xiàn)鎖的獲取和釋放。Synchronized關鍵字可以用在方法上或者代碼塊中。當一個線程執(zhí)行到synchronized修飾的代碼塊時,它會嘗試獲取鎖,如果鎖沒有被其他線程占用,則獲取成功,執(zhí)行代碼塊中的內(nèi)容。如果鎖已經(jīng)被其他線程占用,則該線程會進入等待狀態(tài),直到獲取到鎖才能繼續(xù)執(zhí)行。
1.2Synchronized是什么鎖?
- 開始時是樂觀鎖, 如果鎖沖突頻繁, 就轉(zhuǎn)換為悲觀鎖.
- 開始是輕量級鎖實現(xiàn), 如果鎖被持有的時間較長, 就轉(zhuǎn)換成重量級鎖.
- 實現(xiàn)輕量級鎖的時候大概率用到的自旋鎖策略
- 是一種不公平鎖
- 是一種可重入鎖
- 不是讀寫鎖
?
注:需要使用公平鎖,建議使用ReentrantLock來實現(xiàn)。ReentrantLock提供了公平鎖和非公平鎖兩種模式,通過構(gòu)造函數(shù)的參數(shù)來指定鎖的模式。
1.3Synchronized加鎖工作過程
????????對于鎖資源只有一個或者兩個線程交替競爭的,仍然需要使用系統(tǒng)調(diào)用,無疑對CPU資源是極大的消耗。因此,在jdk1.6針對Synchronized加鎖進行了優(yōu)化。按對鎖的競爭程度劃分成:無鎖,偏向鎖,輕量級鎖,重量級鎖。簡單而言就是從無鎖-->重量級鎖。?
無鎖
當你添加了鎖時,如果編譯器認為不需要加鎖,會自動刪除,因此便是無鎖
偏向鎖
偏向鎖不是真的 "加鎖", 只是給對象頭中做一個 "偏向鎖的標記", 記錄這個鎖屬于哪個線程.
如果后續(xù)沒有其他線程來競爭該鎖, 那么就不用進行其他同步操作了(避免了加鎖解鎖的開銷)
如果后續(xù)有其他線程來競爭該鎖(剛才已經(jīng)在鎖對象中記錄了當前鎖屬于哪個線程了, 很容易識別當前申請鎖的線程是不是之前記錄的線程), 那就取消原來的偏向鎖狀態(tài), 進入一般的輕量級鎖狀態(tài).
注:相當于做個標記,相當于 "延遲加鎖" . 能不加鎖就不加鎖, 盡量避免不必要的加鎖開銷.
輕量級鎖
隨著其他線程進入競爭, 偏向鎖狀態(tài)被消除, 進入輕量級鎖狀態(tài)(自適應的自旋鎖).
此處的輕量級鎖就是通過 CAS 來實現(xiàn)
- 通過 CAS 檢查并更新一塊內(nèi)存 (比如 null => 該線程引用)
- 如果更新成功, 則認為加鎖成功
- 如果更新失敗, 則認為鎖被占用, 繼續(xù)自旋式的等待(并不放棄 CPU).
注:此處的自旋鎖不會一種持續(xù)進行,而是達到一定的時間/重試次數(shù), 就不再自旋了.
重量級鎖
????????如果鎖競爭進一步激烈, 自旋不能快速獲取到鎖狀態(tài), 就會膨脹為重量級鎖此處的重量級鎖就是指用到內(nèi)核提供的 mutex .
具體流程:
- 執(zhí)行加鎖操作, 先進入內(nèi)核態(tài).
- 在內(nèi)核態(tài)判定當前鎖是否已經(jīng)被占用
- 如果該鎖沒有占用, 則加鎖成功, 并切換回用戶態(tài).
- 如果該鎖被占用, 則加鎖失敗. 此時線程進入鎖的等待隊列, 掛起. 等待被操作系統(tǒng)喚醒.
- 經(jīng)歷了一系列的滄海桑田, 這個鎖被其他線程釋放了, 操作系統(tǒng)也想起了這個掛起的線程, 于是喚醒
- 這個線程, 嘗試重新獲取鎖
1.4其他優(yōu)化操作
? ? ? ? 我們額外補充2個編譯器對鎖的優(yōu)化操作。鎖消除和鎖粗化
鎖消除
????????代碼中, 用到了 synchronized, 但其實沒有在多線程環(huán)境下. (例如 StringBuffer)
StringBuffer tq02 = new StringBuffer();
tq02.append("a");
tq02.append("b");
tq02.append("c");
tq02.append("d");
每個 append 的調(diào)用都會涉及加鎖和解鎖. 但如果只是在單線程中執(zhí)行這個代碼, 那么這些加
鎖解鎖操作是沒有必要的, 白白浪費了一些資源開銷.因此將鎖給優(yōu)化了。
鎖粗化
鎖的粗化是根據(jù)鎖的粒度:粗和細
?實際開發(fā)過程中, 使用細粒度鎖, 是期望釋放鎖的時候其他線程能使用鎖.但可能并沒有其他線程來搶占這個鎖. 這種情況 JVM 就會自動把鎖粗化, 避免頻繁申請釋放鎖.
?
二.死鎖
2.1什么是死鎖
????????死鎖是指在多進程系統(tǒng)中,每個進程都在等待某個資源,而該資源又被其他進程占用,導致所有進程都無法繼續(xù)執(zhí)行的狀態(tài)。
例如:A、B、C、D和E去上廁所,A進入廁所并且鎖門,B.C.D等待,可是A剛剛進入廁所,因為特殊的原因,憑空轉(zhuǎn)移到了外面,A就得重新排隊,可是門還是鎖著的啊,因此導致了死鎖。
2.2死鎖的幾個經(jīng)典場景
經(jīng)典場景有:
- 一個線程,一把鎖
- 兩個線程,兩把鎖
- 多個線程,多把鎖
1.一個線程,一把鎖
? ? ? ? 一個線程連續(xù)被同一個加鎖兩次,如果是不可重入鎖,那么會是死鎖。
解析:我去上廁所,我把廁所門鎖住,再通過廁所的窗戶出去,然后再來上廁所,發(fā)現(xiàn)廁所鎖住了,就耐心等待,卻沒想過這是自己鎖的。
代碼實現(xiàn):
public class Counter {
void increase() {
synchronize(this){
increase() //可以理解為翻窗逃走,第二次加鎖時,是鎖了的
}
}
public static void main(String[] args) throws InterruptedException {
final Counter counter = new Counter();
Thread t1 = new Thread(() -> {
counter.increase();
});
t1.start();
}
}
2.兩個線程,兩把鎖
????????線程1先獲取鎖A,再嘗試獲取鎖B,同時,線程2先獲取鎖B,再嘗試獲取鎖A,此時兩個線程就會互相僵住,誰都獲取不到對方持有的鎖。
解析:我在汽車里,車鑰匙在我妻子手上,我出不來,我妻子在房間里,房間鑰匙在我手上,我妻子也出不來,導致雙方被鎖,導致死鎖。
代碼示例:
public class Test {
public static void main(String[] args) {
//2個鎖對象
Object lockerA = new Object();
Object lockerB = new Object();
Thread t1 = new Thread(() -> {
System.out.println("t1嘗試獲取鎖A");
synchronized (lockerA){
System.out.println("t1獲取到鎖A");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1嘗試獲取鎖B");
synchronized (lockerB){
System.out.println("t1獲取到鎖B");
}
}
});
Thread t2 = new Thread(() -> {
System.out.println("t2嘗試獲取鎖B");
synchronized (lockerB){
System.out.println("t2獲取到鎖B");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2嘗試獲取鎖A");
synchronized (lockerA){
System.out.println("t2獲取到鎖A");
}
}
});
t1.start();
t2.start();
}
}
3.多個線程,多把鎖
? ? ? ? 很明顯啊,兩把鎖,兩個線程也有問題,更何況是多把鎖啊,在這方面最經(jīng)典的是"哲學家就餐問題"。
如圖:火柴人是哲學家、紅線是筷子,每一個哲學家的左右都有一根筷子。規(guī)定,當有一根哲學家餓了,會先拿起左邊的筷子,然后再拿右邊的筷子,吃完了就放下筷子。
造成死鎖問題:每一個哲學家都餓了,然后都拿起了左邊的筷子,可是當拿右邊的筷子時,發(fā)現(xiàn)有其他人在使用,所以導致阻塞,然后一直等待別人吃飽放下筷子,可是每個人都在等待。
2.3死鎖產(chǎn)生的條件
死鎖產(chǎn)生需要四個條件:
- 互斥使用,即當資源被一個線程使用(占有)時,別的線程不能使用
- 不可搶占,資源請求者不能強制從資源占有者手中奪取資源,資源只能由資源占有者主動釋放。
- 請求和保持,即當資源請求者在請求其他的資源的同時保持對原有資源的占有。
- 循環(huán)等待,即存在一個等待隊列:P1占有P2的資源,P2占有P3的資源,P3占有P1的資源。這樣就形成了一個等待環(huán)路。
當上述四個條件都成立的時候,便形成死鎖。當然,死鎖的情況下如果打破上述任何一個條件,便可讓死鎖消失。
?
2.4如何解決死鎖
? ? ? ? 想沒有死鎖,那么我們可以從死鎖產(chǎn)生的條件入手,只有破壞其他一條就可以了。?互斥使用和不可搶占是鎖的基本特性,因此無法干預,但是請求和保持,也不可能改變,因為這是代碼執(zhí)行邏輯。因此只有循環(huán)等待,我們可以打破
????????為了解決死鎖問題,可以采取預防、避免、檢測和解除四種方法。
預防:通過設置某些限制條件,以防止死鎖的發(fā)生。
避免:系統(tǒng)在分配資源時根據(jù)資源的使用情況提前作出預測,從而避免死鎖的發(fā)生。
檢測:允許系統(tǒng)在運行過程中產(chǎn)生死鎖,但系統(tǒng)中有相應的管理模塊可以及時檢測出已經(jīng)產(chǎn)生的死鎖,并精確地確定與死鎖有關的進程和資源,然后采取適當措施清除系統(tǒng)中已經(jīng)產(chǎn)生的死鎖。
解除:當發(fā)現(xiàn)有進程死鎖后,立即解脫它從死鎖狀態(tài)中出來。常用的方法包括剝奪資源和撤銷進程。剝奪資源是從其他進程中剝奪足夠數(shù)量的資源給死鎖進程,以解除死鎖狀態(tài)。撤銷進程可以直接撤銷死鎖進程或撤銷代價最小的進程,直至有足夠的資源可用,從而消除死鎖狀態(tài)。文章來源:http://www.zghlxwxcb.cn/news/detail-644636.html
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ????????????????---------------------懶惰的tq02文章來源地址http://www.zghlxwxcb.cn/news/detail-644636.html
到了這里,關于Java多線程(4)---死鎖和Synchronized加鎖流程的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關文章,希望大家以后多多支持TOY模板網(wǎng)!