來源:blog.csdn.net/xiewenfeng520/article/details/107230996
前言
只對死鎖代碼感興趣的可以直接跳到第三小節 必然死鎖示例,如果對死鎖還不太了解的,我們可以一起來討論以下幾個議題
- 什么是死鎖?
- 死鎖有什么危害和特點?
- 代碼實作一個必然死鎖的示例
- 分析死鎖的程序
1.什么是死鎖?
關鍵詞:并發場景,多執行緒
首先我們需要知道,死鎖一定發生在并發場景中,我們為了保證執行緒安全,有時會給程式使用各種能保證并發安全的工具,尤其是鎖,但是如果在使用程序中處理不得當,就有可能會導致發生死鎖的情況,
關鍵詞:互不相讓
死鎖是一種狀態,當兩個(或多個)執行緒(或行程)相互持有對方所需要的資源,卻又都不主動釋放自己手中所持有的資源,導致大家都獲取不到自己想要的資源,所有相關的執行緒(或行程)都無法繼續往下執行,在未改變這種狀態之前都不能向前推進,我們就把這種狀態稱為死鎖狀態,認為它們發生了死鎖,
簡而言之,死鎖就是兩個或多個執行緒(或行程)被無限期地阻塞,相互等待對方手中資源的一種狀態,
兩個執行緒死鎖的情況

如圖所示,執行緒1 已經持有了 鎖1,同時 執行緒2 也已經持有了鎖2,然后 執行緒1 嘗試獲取 鎖2,但是 執行緒2 并沒有釋放 鎖2,所以 執行緒1 處于阻塞狀態,同理可知,圖中的 執行緒2 獲取 鎖1也會被阻塞,
這樣一來,執行緒1 和 執行緒2 就發生了死鎖,因為它們都相互持有對方想要的資源,卻又不釋放自己手中的資源,形成相互等待,而且會一直等待下去,
2.死鎖的影響和危害
2.1 死鎖的影響
死鎖的影響在不同系統中是不一樣的,影響的大小一部分取決于當前這個系統或者環境對死鎖的處理能力,
2.1.1 資料庫中
例如,在資料庫系統軟體的設計中,考慮了監測死鎖以及從死鎖中恢復的情況,在執行一個事務的時候可能需要獲取多把鎖,并一直持有這些鎖直到事務完成,在某個事務中持有的鎖可能在其他事務中也需要,因此在兩個事務之間有可能發生死鎖的情況,一旦發生了死鎖,如果沒有外部干涉,那么兩個事務就會永遠的等待下去,
但資料庫系統不會放任這種情況發生,當資料庫檢測到這一組事務發生了死鎖時,根據策略的不同,可能會選擇放棄某一個事務,被放棄的事務就會釋放掉它所持有的鎖,從而使其他的事務繼續順利進行,
此時程式可以重新執行被強行終止的事務,而這個事務現在就可以順利執行了,因為所有跟它競爭資源的事務都已經在剛才執行完畢,并且釋放資源了,
2.1.2 JVM 中
在 JVM 中,對于死鎖的處理能力就不如資料庫那么強大了,如果在 JVM 中發生了死鎖,JVM 并不會自動進行處理,所以一旦死鎖發生,就會陷入無窮的等待,
2.2 死鎖的危害以及特點
關鍵詞:概率性事件
死鎖的問題和其他的并發安全問題一樣,是概率性的,也就是說,即使存在發生死鎖的可能性,也并不是 100% 會發生的,如果每個鎖的持有時間很短,那么發生沖突的概率就很低,所以死鎖發生的概率也很低,但是在線上系統里,可能每天有幾千萬次的“獲取鎖”、“釋放鎖”操作,在巨量的次數面前,整個系統發生問題的幾率就會被放大,只要有某幾次操作是有風險的,就可能會導致死鎖的發生,
也正是因為死鎖“不一定會發生”的特點,導致提前找出死鎖成為了一個難題,壓力測驗雖然可以檢測出一部分可能發生死鎖的情況,但是并不足以完全模擬真實、長期運行的場景,因此沒有辦法把所有潛在可能發生死鎖的代碼都找出來,
關鍵詞:危害大,發生幾率不高
一旦發生了死鎖,根據發生死鎖的執行緒的職責不同,就可能會造成 子系統崩潰、性能降低 甚至 整個系統崩潰 等各種不良后果,而且死鎖往往發生在高并發、高負載的情況下,因為可能會直接影響到很多用戶,造成一系列的問題,以上就是死鎖發生幾率不高但是危害大的特點,
3.必然死鎖示例
public class MustDeadLockDemo {
public static void main(String[] args) {
Object lock1 = new Object();
Object lock2 = new Object();
new Thread(new DeadLockTask(lock1, lock2, true), "執行緒1").start();
new Thread(new DeadLockTask(lock1, lock2, false), "執行緒2").start();
}
static class DeadLockTask implements Runnable {
private boolean flag;
private Object lock1;
private Object lock2;
public DeadLockTask(Object lock1, Object lock2, boolean flag) {
this.lock1 = lock1;
this.lock2 = lock2;
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "->拿到鎖1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->等待鎖2釋放...");
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "->拿到鎖2");
}
}
}
if (!flag) {
synchronized (lock2) {
System.out.println(Thread.currentThread().getName() + "->拿到鎖2");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "->等待鎖1釋放...");
synchronized (lock1) {
System.out.println(Thread.currentThread().getName() + "->拿到鎖1");
}
}
}
}
}
}
執行結果:

可以看到程式一直處于阻塞狀態,
4.程序分析
其實上面的代碼示例發生死鎖的程序就是第一小節中 兩個執行緒發生死鎖 的情況,這里我們把圖拿過來,方便分析,

本文使用 IDEA 進行除錯,將斷點打在 33 行,run方法的第一行,選擇 Thread 模式,
注意:除錯程序,因為有人為的等待時間,所以并不會發生死鎖,這里只是演示執行緒執行的順序和狀態,

第一步,執行緒1進入,flag = true,進入第一個 synchronized 同步塊,拿到 lock1(鎖1)

第二步,直接點擊 Resume Program(F9),進入執行緒2,此時 flag = false,進入第二個 synchronized 同步塊

當然如果 Thread.sleep 的時間夠長,或者操作速度夠快的話,也能發生死鎖,
5.總結
本章我們討論了什么是死鎖,以及死鎖的影響和危害,演示了一個必然死鎖的例子,然后使用 IDEA 工具除錯了兩個執行緒發生死鎖的步驟,
在 JVM 中如果發生死鎖,可能會導致程式部分甚至全部無法繼續向下執行的情況,所以死鎖在 JVM 中所帶來的危害和影響是比較大的,我們需要盡量避免,
最后如果在面試中碰到這一題,希望大家都能順利通過,
參考:《Java 并發編程 78 講》- 徐隆曦
近期熱文推薦:
1.1,000+ 道 Java面試題及答案整理(2021最新版)
2.別在再滿屏的 if/ else 了,試試策略模式,真香!!
3.臥槽!Java 中的 xx ≠ null 是什么新語法?
4.Spring Boot 2.5 重磅發布,黑暗模式太炸了!
5.《Java開發手冊(嵩山版)》最新發布,速速下載!
覺得不錯,別忘了隨手點贊+轉發哦!
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/353118.html
標籤:Java
