關於死鎖,一直是面試和日常開發中的熟悉話題,本文將進行一下探討:
- 什么是死鎖
- 出現死鎖的原因
- 如何避免死鎖
- 代碼中死鎖問題怎么排查
@
1. 什么是死鎖
死鎖是指兩個或兩個以上的進程或線程,在執行過程中,由於競爭資源而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去
划重點:兩個或兩個以上進程或線程,競爭資源,最終阻塞無法進行下去
這里可能會問:外力作用是什么外力?資源剝奪;撤銷進程;進程回退等
2. 出現死鎖的原因
-
系統資源不足;
-
進程推進順序非法
-
系統資源分配不當。
另外,信號量使用不當也會造成死鎖。比如A等B消息,B等A的消息
死鎖產生的有4個必要條件
(1) 互斥條件:一個資源每次只能被一個進程使用。
(2) 請求與保持條件:一個進程因請求資源而阻塞時,對已獲得的資源保持不放。
(3) 不剝奪條件:進程已獲得的資源,在末使用完之前,不能強行剝奪。
(4) 循環等待條件:若干進程之間形成一種頭尾相接的循環等待資源關系。
3. 如何預防和避免死鎖
- 預防:
死鎖的預防基本思想打破產生死鎖的四個必要條件中的一個或幾個,保證系統不會進入死鎖狀態。
比如:
打破互斥條件:允許進程同時訪問某些資源
打破不剝奪條件:允許進程從占有者占有的資源中強行剝奪一些資源
打破請求與保持條件:進程在運行前一次性地向系統申請它所需要的全部資源
打破循環等待條件:實行資源有序分配策略
- 避免:
- 加鎖順序(線程按照一定的順序加鎖)
- 加鎖時限(線程嘗試獲取鎖的時候加上一定的時限,超過時限則放棄對該鎖的請求,並釋放自己占有的鎖)
- 死鎖檢測
阿里巴巴中最新的開發規約,里面有對避免死鎖的說明,具體如下:
【強制】對多個資源、數據庫表、對象同時加鎖時,需要保持一致的加鎖順序,否則可能會造成死鎖。 說明:線程一需要對表 A、B、C 依次全部加鎖后才可以進行更新操作,那么線程二的加鎖順序也必須是 A、B、C,否則可能出現死鎖。
4. 實戰JVM死鎖問題排查
4.1 死鎖代碼案例
按照死鎖產生原則,可寫出一個產生死鎖的程序
public class DeadLock {
//創建兩個對象,用兩個線程分別先后獨占
private Boolean flag1 = true;
private Boolean flag2 = false;
public static void main(String[] args) {
DeadLock deadLock = new DeadLock();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程1開始,作用是當flag1 = true 時,將flag2也改為 true");
synchronized (deadLock.flag1){
if(deadLock.flag1){
try{
//睡眠1s ,模擬業務執行耗時,並保證兩個線程進入死鎖狀態
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("flag1 = true,准備鎖住flag2...");
synchronized (deadLock.flag2){
deadLock.flag2 = true;
}
}
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("線程2開始,作用是當flag2 = false 時,將flag1也改為 false");
synchronized (deadLock.flag2){
if(!deadLock.flag2){
try{
//睡眠1s ,模擬業務執行耗時,並保證兩個線程進入死鎖狀態
Thread.sleep(1000);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println("flag2 = false,准備鎖住flag1...");
synchronized (deadLock.flag1){
deadLock.flag1 = false;
}
}
}
}
}).start();
}
}
以上代碼,可以用一個死鎖的圖解釋。線程1獨占對象1,想要訪問對象2,而對象2此時已經獨占對象2,在等待對象1的資源釋放,此時線程1因無法獲取到對象2而無法向下執行,因此沒法釋放對象1,線程2同理,造成了死鎖狀態,兩個線程都阻塞在等待資源處
4.2 死鎖問題JVM工具排查
4.2.1 jps+jstack方式排查
-
查找程序運行端口
> jps -l 18714 sun.tools.jps.Jps 18703 jvm.DeadLock
-
jstack打印堆棧信息,發現死鎖存在的位置,進行排查
> jstack -l 18703

4.2.2 jconsole方式排查
mac下:輸入jconsol命令通過可視化界面連接

選擇線程,監測死鎖。會將死鎖的線程信息都展示出來
4.2.3 jvisualvm方式
用命令行召喚出jconsole,選擇對應進程即可直觀看到死鎖的存在
