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