Java死鎖問題解決方案及示例詳解
1、簡(jiǎn)述
死鎖(Deadlock) 是指兩個(gè)或多個(gè)線程因爭(zhēng)奪資源而相互等待,導(dǎo)致所有線程都無(wú)法繼續(xù)執(zhí)行的一種狀態(tài)。
死鎖的四個(gè)必要條件:
- 互斥條件:某資源一次只能被一個(gè)線程占用。
- 占有且等待:一個(gè)線程已持有資源并等待其他線程的資源。
- 不可剝奪:資源不能被強(qiáng)行從線程中剝奪。
- 循環(huán)等待:多個(gè)線程形成一種頭尾相接的等待資源關(guān)系鏈。
只要這四個(gè)條件同時(shí)滿足,就可能產(chǎn)生死鎖。
2、死鎖示例代碼
下面是一個(gè)典型的死鎖示例:
public class DeadlockExample {
private static final Object LockA = new Object();
private static final Object LockB = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (LockA) {
System.out.println("Thread-1: locked A");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (LockB) {
System.out.println("Thread-1: locked B");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (LockB) {
System.out.println("Thread-2: locked B");
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (LockA) {
System.out.println("Thread-2: locked A");
}
}
});
t1.start();
t2.start();
}
}
運(yùn)行這段程序可能會(huì)導(dǎo)致 Thread-1 等待 LockB,Thread-2 等待 LockA,從而產(chǎn)生死鎖。
3、如何檢測(cè)死鎖?
3.1 使用 jstack
當(dāng)應(yīng)用卡住時(shí),使用如下命令查看線程堆棧:
jps # 查找 Java 進(jìn)程 ID jstack <pid>
你會(huì)看到類似:
Found one Java-level deadlock: "Thread-1": waiting to lock monitor 0x000..., which is held by "Thread-2" ...
3.2 使用 VisualVM 或 JConsole
這類工具可以圖形化展示線程狀態(tài),識(shí)別死鎖非常直觀。
4、如何預(yù)防和解決死鎖?
4.1 統(tǒng)一資源獲取順序(推薦)
確保多個(gè)線程獲取多個(gè)鎖時(shí) 按相同順序加鎖。
public void safeMethod() {
synchronized (LockA) {
synchronized (LockB) {
// 安全操作
}
}
}
4.2 使用 tryLock() 避免無(wú)限等待
使用 ReentrantLock 的 tryLock() 方法設(shè)置獲取鎖的超時(shí)時(shí)間。
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
public class TryLockExample {
private final ReentrantLock lockA = new ReentrantLock();
private final ReentrantLock lockB = new ReentrantLock();
public void run() {
Thread t1 = new Thread(() -> {
try {
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread-1: locked A");
Thread.sleep(100);
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread-1: locked B");
lockB.unlock();
}
lockA.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
try {
if (lockB.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread-2: locked B");
Thread.sleep(100);
if (lockA.tryLock(1, TimeUnit.SECONDS)) {
System.out.println("Thread-2: locked A");
lockA.unlock();
}
lockB.unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
}
public static void main(String[] args) {
new TryLockExample().run();
}
}
4.3 使用 java.util.concurrent 包
避免使用低級(jí)的 synchronized,使用高級(jí)并發(fā)工具如 ExecutorService、Semaphore、Lock 等更可控的工具。
4.4 死鎖檢測(cè)與恢復(fù)
一些大型系統(tǒng)中,可以通過定期掃描線程狀態(tài),自動(dòng)檢測(cè)死鎖并重啟部分線程或服務(wù)。例如通過自定義 ThreadMXBean 檢測(cè)死鎖。
最佳實(shí)踐小結(jié):
| 技術(shù)手段 | 說明 |
|---|---|
| 加鎖順序統(tǒng)一 | 所有線程按相同順序獲取資源 |
| tryLock + timeout | 嘗試加鎖失敗后避免長(zhǎng)時(shí)間阻塞 |
| lock 分解 | 將大鎖拆分成小鎖減少競(jìng)爭(zhēng) |
| 并發(fā)工具替代 synchronized | 使用 ReentrantLock、Semaphore 等 |
| 死鎖檢測(cè)工具 | 使用 jstack、VisualVM 等工具 |
5、結(jié)語(yǔ)
死鎖是多線程編程中不可忽視的問題,但并不是無(wú)法避免。只要我們?cè)谠O(shè)計(jì)時(shí)保持鎖的有序性,并結(jié)合現(xiàn)代并發(fā)工具進(jìn)行控制,絕大多數(shù)死鎖問題都是可以預(yù)防的。
以上就是Java死鎖問題解決方案及示例詳解的詳細(xì)內(nèi)容,更多關(guān)于Java死鎖問題解決的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Spring Cloud Feign統(tǒng)一設(shè)置驗(yàn)證token實(shí)現(xiàn)方法解析
這篇文章主要介紹了Spring Cloud Feign統(tǒng)一設(shè)置驗(yàn)證token實(shí)現(xiàn)方法解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-08-08
SpringBoot框架RESTful接口設(shè)置跨域允許
這篇文章主要為大家詳細(xì)介紹了SpringBoot框架RESTful接口設(shè)置跨域允許,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08
java對(duì)象轉(zhuǎn)化成String類型的四種方法小結(jié)
在java項(xiàng)目的實(shí)際開發(fā)和應(yīng)用中,常常需要用到將對(duì)象轉(zhuǎn)為String這一基本功能。本文就詳細(xì)的介紹幾種方法,感興趣的可以了解一下2021-08-08

