Java死鎖問題解決方案及示例詳解
1、簡述
死鎖(Deadlock) 是指兩個(gè)或多個(gè)線程因爭奪資源而相互等待,導(dǎo)致所有線程都無法繼續(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() 避免無限等待
使用 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 | 嘗試加鎖失敗后避免長時(shí)間阻塞 |
lock 分解 | 將大鎖拆分成小鎖減少競爭 |
并發(fā)工具替代 synchronized | 使用 ReentrantLock、Semaphore 等 |
死鎖檢測(cè)工具 | 使用 jstack、VisualVM 等工具 |
5、結(jié)語
死鎖是多線程編程中不可忽視的問題,但并不是無法避免。只要我們?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-08SpringBoot框架RESTful接口設(shè)置跨域允許
這篇文章主要為大家詳細(xì)介紹了SpringBoot框架RESTful接口設(shè)置跨域允許,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-08-08java對(duì)象轉(zhuǎn)化成String類型的四種方法小結(jié)
在java項(xiàng)目的實(shí)際開發(fā)和應(yīng)用中,常常需要用到將對(duì)象轉(zhuǎn)為String這一基本功能。本文就詳細(xì)的介紹幾種方法,感興趣的可以了解一下2021-08-08