死鎖
死鎖的定義
發生在并發中
當兩個執行緒(或更多)執行緒(或執行緒)相互持有對方所需要的資源,又不主動釋放,導致所有執行緒都無法繼續執行,是程式陷入無盡的阻塞,這就是死鎖,
如果多個執行緒之間的依賴關系是環形,存在環形的鎖的依賴關系,那么也可能會發生死鎖,
死鎖的影響
死鎖的影響在不同的系統中是不一樣的,這取決于系統對死鎖的處理能力,
- 資料庫中:檢測并放棄事務;
- JVM中:無法自動處理,但是提供了工具可以幫助我們取檢測;
程式中的死鎖
- 一旦發生,多是高并發場景,影響用戶多;
- 整個系統崩潰,子系統崩潰,性能降低;
- 壓力測驗無法找出所有潛在的死鎖;
例子
例一
public class MustDeadLock extends Thread {
int flag = 1;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
MustDeadLock run1 = new MustDeadLock();
MustDeadLock run2 = new MustDeadLock();
run1.flag = 1;
run2.flag = 0;
run1.start();
run2.start();
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (o1) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("執行緒1成功拿到兩把鎖!");
}
}
}
if (flag == 0) {
synchronized (o2) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("執行緒2成功拿到兩把鎖!");
}
}
}
}
}
案例分析
- 當類的物件 flag=1 時(T1),先鎖定 O1,睡眠 500 毫秒,然后鎖定 O2;
- T1 在睡眠的程序中,另一個 flag=0(T2)執行緒啟動,先鎖定 O2,睡眠 500 毫秒,等待 T1 釋放 O1;
- T1 睡眠結束后需要鎖定 O2 才能繼續執行,而此時 O2 已被 T2 鎖定;
- T2 睡眠結束后需要鎖定 O1 才能繼續執行,而此時 O1 已被 T1 鎖定;
- 此時 T1,T2 相互等待,都需要對方鎖定的資源才能繼續執行,于是便發生死鎖了,
例二(轉賬操作)
public class TransferMoney implements Runnable {
int flag = 1;
static Account a = new Account(500);
static Account b = new Account(500);
public static void main(String[] args) throws InterruptedException {
TransferMoney r1 = new TransferMoney();
TransferMoney r2 = new TransferMoney();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("a.balance = " + a.balance);
System.out.println("b.balance = " + b.balance);
}
@Override
public void run() {
if (flag == 1) {
transferMoney(a, b, 200);
}
if (flag == 0) {
transferMoney(b, a, 200);
}
}
private void transferMoney(Account from, Account to, int amount) {
synchronized (from) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (to) {
if (from.balance - to.balance < 0) {
System.out.println("余額不足,轉賬失敗!");
}
from.balance -= to.balance;
to.balance += from.balance;
System.out.println("轉賬成功,轉賬共:" + amount);
}
}
}
static class Account {
int balance;
public Account(int balance) {
this.balance = balance;
}
}
}
死鎖產生的必要條件
產生死鎖必須同時滿足以下四個條件,只要其中一條不成立,死鎖就不會發生,
① 互斥條件
行程要求對所分配的資源(如列印機)進行排他性控制,即在一段時間內某資源僅為一個行程所占有,此時若有其他行程請求該資源,則請求行程只能等待,
② 請求與保持條件
行程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他行程占有,此時請求行程被阻塞,但對自己已獲得的資源保持不放,
③ 不剝奪條件
行程已經保持了至少一個資源,但又提出了新的資源請求,而該資源已被其他行程占有,此時請求行程被阻塞,但對自己已獲得的資源保持不放,
④ 回圈等待條件
存在一種行程資源的回圈等待鏈,鏈中每一個行程已獲得的資源同時被鏈中下一個行程所請求,即存在一個處于等待狀態的行程集合{Pl, P2, ..., pn},其中Pi等 待的資源被P(i+1)占有(i=0, 1, ..., n-1),Pn等待的資源被P0占有,如例一所示,
如何定位死鎖?
jstack
這里以案例一為基礎進行展示,
第一步:先運行列一;
第二步:找到 Java 在系統中的行程 id;
方式一(直接通過任務管理器獲取)
-
我是在 Windows 環境下進行演示,我可以先打開任務管理,然后再運行例一;
-
找到對應的進行 id;
-
方式二(通過 Java 提供的程式獲取)
-
找到 Java JDK 的安裝路徑下的 bin 目錄;
-
在此目錄下打開 CMD 視窗;
-
然后直接運行 jps 后就會列印出我們正在運行的行程 id;
-
D:\development\jdk\1.8jdk\bin>jps 12160 Jps 45224 Launcher 23020 MustDeadLock
第三步:通過工具定位到死鎖
還是在 Java JDK 的安裝路徑下的 bin 目錄下打開 cmd 視窗,然后運行 jstack + 行程id
D:\development\jdk\1.8jdk\bin>jstack 23020
2022-03-06 17:13:11
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.281-b09 mixed mode):
"DestroyJavaVM" #14 prio=5 os_prio=0 tid=0x00000204548cb000 nid=0x12220 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Thread-1" #13 prio=5 os_prio=0 tid=0x00000204719b6000 nid=0xbd44 waiting for monitor entry [0x0000000c528ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:45)
- waiting to lock <0x000000076c09a3a0> (a java.lang.Object)
- locked <0x000000076c09a3b0> (a java.lang.Object)
"Thread-0" #12 prio=5 os_prio=0 tid=0x00000204719b3000 nid=0xd260 waiting for monitor entry [0x0000000c527ff000]
java.lang.Thread.State: BLOCKED (on object monitor)
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:33)
- waiting to lock <0x000000076c09a3b0> (a java.lang.Object)
- locked <0x000000076c09a3a0> (a java.lang.Object)
"Service Thread" #11 daemon prio=9 os_prio=0 tid=0x000002047198c800 nid=0x48f4 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C1 CompilerThread3" #10 daemon prio=9 os_prio=2 tid=0x00000204718eb800 nid=0xa35c waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread2" #9 daemon prio=9 os_prio=2 tid=0x00000204718e6800 nid=0x9288 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread1" #8 daemon prio=9 os_prio=2 tid=0x00000204718e4000 nid=0x7dcc waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"C2 CompilerThread0" #7 daemon prio=9 os_prio=2 tid=0x00000204718e1000 nid=0x101a4 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Monitor Ctrl-Break" #6 daemon prio=5 os_prio=0 tid=0x00000204718de000 nid=0xb3b4 runnable [0x0000000c520fe000]
java.lang.Thread.State: RUNNABLE
at java.net.SocketInputStream.socketRead0(Native Method)
at java.net.SocketInputStream.socketRead(SocketInputStream.java:116)
at java.net.SocketInputStream.read(SocketInputStream.java:171)
at java.net.SocketInputStream.read(SocketInputStream.java:141)
at sun.nio.cs.StreamDecoder.readBytes(StreamDecoder.java:284)
at sun.nio.cs.StreamDecoder.implRead(StreamDecoder.java:326)
at sun.nio.cs.StreamDecoder.read(StreamDecoder.java:178)
- locked <0x000000076bf8f8a0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(InputStreamReader.java:184)
at java.io.BufferedReader.fill(BufferedReader.java:161)
at java.io.BufferedReader.readLine(BufferedReader.java:324)
- locked <0x000000076bf8f8a0> (a java.io.InputStreamReader)
at java.io.BufferedReader.readLine(BufferedReader.java:389)
at com.intellij.rt.execution.application.AppMainV2$1.run(AppMainV2.java:47)
"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x0000020471893000 nid=0x109e0 waiting on condition [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x0000020471892800 nid=0x6798 runnable [0x0000000000000000]
java.lang.Thread.State: RUNNABLE
"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000002046f4cb000 nid=0x5848 in Object.wait() [0x0000000c51dfe000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076be08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
- locked <0x000000076be08ee0> (a java.lang.ref.ReferenceQueue$Lock)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)
"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000002046f4c4800 nid=0xee60 in Object.wait() [0x0000000c51cff000]
java.lang.Thread.State: WAITING (on object monitor)
at java.lang.Object.wait(Native Method)
- waiting on <0x000000076be06c00> (a java.lang.ref.Reference$Lock)
at java.lang.Object.wait(Object.java:502)
at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
- locked <0x000000076be06c00> (a java.lang.ref.Reference$Lock)
at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)
"VM Thread" os_prio=2 tid=0x000002046f499000 nid=0x7284 runnable
"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x00000204548e1800 nid=0xf530 runnable
"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x00000204548e3000 nid=0x12aa0 runnable
"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x00000204548e4000 nid=0x11d58 runnable
"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x00000204548e5800 nid=0x67d8 runnable
"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x00000204548e7800 nid=0xed20 runnable
"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x00000204548e8800 nid=0x2e10 runnable
"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x00000204548eb800 nid=0xd504 runnable
"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x00000204548ec800 nid=0x10cc0 runnable
"GC task thread#8 (ParallelGC)" os_prio=0 tid=0x00000204548ed800 nid=0x8548 runnable
"GC task thread#9 (ParallelGC)" os_prio=0 tid=0x00000204548ee800 nid=0xac70 runnable
"VM Periodic Task Thread" os_prio=2 tid=0x00000204719b1000 nid=0x4b64 waiting on condition
JNI global references: 12
Found one Java-level deadlock:
=============================
"Thread-1":
waiting to lock monitor 0x000002046f4c8368 (object 0x000000076c09a3a0, a java.lang.Object),
which is held by "Thread-0"
"Thread-0":
waiting to lock monitor 0x000002046f4caca8 (object 0x000000076c09a3b0, a java.lang.Object),
which is held by "Thread-1"
Java stack information for the threads listed above:
===================================================
"Thread-1":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:45)
- waiting to lock <0x000000076c09a3a0> (a java.lang.Object)
- locked <0x000000076c09a3b0> (a java.lang.Object)
"Thread-0":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:33)
- waiting to lock <0x000000076c09a3b0> (a java.lang.Object)
- locked <0x000000076c09a3a0> (a java.lang.Object)
Found 1 deadlock.
運行后主要觀察
Java stack information for the threads listed above:
===================================================
"Thread-1":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:45)
- waiting to lock <0x000000076c09a3a0> (a java.lang.Object)
- locked <0x000000076c09a3b0> (a java.lang.Object)
"Thread-0":
at main.threaddemo.MustDeadLock.run(MustDeadLock.java:33)
- waiting to lock <0x000000076c09a3b0> (a java.lang.Object)
- locked <0x000000076c09a3a0> (a java.lang.Object)
Found 1 deadlock.
通過 ThreadMXBean 工具類去檢測死鎖
代碼
public class ThreadMXBeanDetection implements Runnable {
int flag = 1;
static Object lock1 = new Object();
static Object lock2 = new Object();
public static void main(String[] args) throws InterruptedException {
ThreadMXBeanDetection r1 = new ThreadMXBeanDetection();
ThreadMXBeanDetection r2 = new ThreadMXBeanDetection();
r1.flag = 1;
r2.flag = 0;
Thread t1 = new Thread(r1);
Thread t2 = new Thread(r2);
t1.start();
t2.start();
Thread.sleep(3000);
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
for (int i = 0; i < deadlockedThreads.length; i++) {
ThreadInfo threadInfo = threadMXBean.getThreadInfo(deadlockedThreads[i]);
System.out.println("發現死鎖" + threadInfo.getThreadName());
}
}
}
@Override
public void run() {
System.out.println("flag = " + flag);
if (flag == 1) {
synchronized (lock1) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("執行緒1成功拿到兩把鎖!");
}
}
}
if (flag == 0) {
synchronized (lock2) {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("執行緒2成功拿到兩把鎖!");
}
}
}
}
}
輸出
flag = 1
flag = 0
發現死鎖Thread-1
發現死鎖Thread-0
常見的修復方式
①避免策略
避免相反的獲取鎖的順序,也就是在撰寫程式的時候就規劃好鎖的獲取,從而破壞死鎖產生的四個必要條件的其中一個,
②檢測與恢復策略
檢測到鎖的時候再將其恢復,不過這個時候已經產生了一定的影響了,
實際開發中如何避免死鎖
- 設定超時時間;
- 多使用并發類而不是自己設計鎖;
- 盡量降低鎖的使用粒度:用不同的鎖而不是一個鎖;
- 如果能使用同步代碼塊,就不使用同步方法:自已指定所物件;
- 創建執行緒的時候命名盡量達到見名知義,方便后面排查問題;
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/440522.html
標籤:其他
