一文詳解查看java死鎖具體怎么做以及怎么避免
前言
在 Java 應(yīng)用中,死鎖(Deadlock) 是指兩個(gè)或多個(gè)線程在執(zhí)行過程中,因爭(zhēng)奪資源而造成的一種互相等待的現(xiàn)象,導(dǎo)致這些線程都無法繼續(xù)執(zhí)行下去,程序“卡死”。這是一種非常典型的并發(fā)問題。
一、什么是 Java 死鎖?
死鎖產(chǎn)生的四個(gè)必要條件(經(jīng)典條件,缺一不可):
- 互斥條件:資源一次只能由一個(gè)線程占用。
- 占有并等待:線程持有至少一個(gè)資源,并等待獲取其他被占用的資源。
- 非搶占條件:線程已獲得的資源在未使用完之前不能被其他線程強(qiáng)行奪取,必須自己釋放。
- 循環(huán)等待條件:存在一個(gè)線程的循環(huán)等待鏈,每個(gè)線程都在等待下一個(gè)線程所占用的資源。
二、如何查看 / 檢測(cè) Java 中的死鎖?
Java 提供了多種方式來 檢測(cè)死鎖,下面介紹幾種 最常用、最實(shí)用的方法:
方法一:使用jstack 工具查看死鎖(推薦,簡(jiǎn)單有效)
步驟:
找到 Java 應(yīng)用的進(jìn)程 ID(PID)
在 Linux / macOS 終端 或 Windows 的命令行下運(yùn)行:
jps
輸出類似:
12345 MyApp 6789 Jps
其中
12345就是你的 Java 應(yīng)用的進(jìn)程ID(PID)。你也可以使用
ps -ef | grep java(Linux/macOS)或任務(wù)管理器(Windows)查找。使用 jstack 查看線程堆棧,并檢測(cè)死鎖
執(zhí)行:
jstack <PID>
例如:
jstack 12345
在輸出內(nèi)容中搜索關(guān)鍵字:deadlock 或 Found one Java-level deadlock
如果存在死鎖,jstack 會(huì)自動(dòng)檢測(cè)并在輸出中明確標(biāo)識(shí)出來,例如:
Found one Java-level deadlock: ============================= "Thread-1": waiting to lock monitor 0x00007f... (object 0x00000000d5d8a3d0, a java.lang.Object), which is held by "Thread-0" "Thread-0": waiting to lock monitor 0x00007f... (object 0x00000000d5d8a3e0, a java.lang.Object), which is held by "Thread-1"
它會(huì)清楚地告訴你哪些線程在互相等待哪些鎖,從而形成死鎖環(huán)。
?? 小技巧:
- 如果輸出內(nèi)容太多,可以將結(jié)果重定向到文件查看:然后打開
jstack 12345 > thread_dump.txt
thread_dump.txt搜索deadlock或dead-lock。
方法二:使用jconsole 或 VisualVM 圖形化工具查看死鎖
這些是 Java 自帶的 GUI 工具,可以直觀地查看線程狀態(tài)和死鎖。
1.jconsole(JDK 自帶)
- 啟動(dòng)方式:
jconsole
- 在彈出的界面中,選擇你的 Java 進(jìn)程;
- 切換到 “線程” 標(biāo)簽頁;
- 點(diǎn)擊 “檢測(cè)死鎖”(Detect Deadlock) 按鈕;
- 如果存在死鎖,它會(huì)列出死鎖涉及的線程和鎖信息。
2.VisualVM(JDK 自帶,功能更強(qiáng)大)
- 啟動(dòng)方式:
jvisualvm
- 選擇你的 Java 進(jìn)程;
- 切換到 Threads(線程) 標(biāo)簽;
- 如果存在死鎖,會(huì)有明確的提示,也可以看到線程互相等待的情況;
- 也可以使用插件增強(qiáng)功能(如 Visual GC 等)。
?? 這兩個(gè)工具適合開發(fā)和測(cè)試環(huán)境,不需要額外安裝,JDK 中自帶有。
方法三:在代碼中編程檢測(cè)死鎖(高級(jí)用法,不常用)
Java 提供了一個(gè) ThreadMXBean 接口,可以 以編程方式檢測(cè)死鎖。
示例代碼:
import java.lang.management.ManagementFactory;
import java.lang.management.ThreadMXBean;
public class DeadlockDetector {
public static void main(String[] args) {
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
if (deadlockedThreads != null && deadlockedThreads.length > 0) {
System.err.println("檢測(cè)到死鎖!涉及以下線程:");
for (long threadId : deadlockedThreads) {
java.lang.management.ThreadInfo threadInfo = threadMXBean.getThreadInfo(threadId);
System.err.println(threadInfo.getThreadName() + " 正在等待鎖,被 " +
threadInfo.getLockOwnerName() + " 持有");
}
} else {
System.out.println("未檢測(cè)到死鎖。");
}
}
}
你可以定期調(diào)用這個(gè)檢測(cè)邏輯(比如通過定時(shí)任務(wù)),或在監(jiān)控系統(tǒng)中集成,用于自動(dòng)化檢測(cè)死鎖。
三、如何避免和解決死鎖?
檢測(cè)到死鎖不是終點(diǎn),關(guān)鍵還是要 避免死鎖發(fā)生,以下是常見的 死鎖預(yù)防 / 解決策略:
1.避免嵌套鎖 / 按固定順序獲取鎖
死鎖常常發(fā)生在多個(gè)線程以不同順序獲取多個(gè)鎖時(shí)。
解決方案:
- 所有線程按照相同的順序獲取鎖,例如總是先獲取鎖A,再獲取鎖B,不要有的線程先A后B,有的先B后A。
?? 反面例子(容易死鎖):
// 線程1:鎖A -> 鎖B
// 線程2:鎖B -> 鎖A
synchronized(lockA) {
synchronized(lockB) { ... }
}
? 正確做法:統(tǒng)一順序,如都先獲取 lockA,再 lockB
2.使用鎖超時(shí)機(jī)制
使用如 ReentrantLock.tryLock(long timeout, TimeUnit unit) 方法,嘗試獲取鎖,如果在指定時(shí)間內(nèi)獲取不到,就放棄,避免一直等待。
示例:
if (lock.tryLock(1, TimeUnit.SECONDS)) {
try {
// 執(zhí)行業(yè)務(wù)
} finally {
lock.unlock();
}
} else {
// 獲取鎖失敗,做相應(yīng)處理,避免死等
}
3.減少同步代碼塊的范圍
- 只對(duì)必要的代碼加鎖,盡快釋放鎖;
- 避免在持有多個(gè)鎖的情況下執(zhí)行耗時(shí)操作(如 IO、網(wǎng)絡(luò)請(qǐng)求等)。
4.使用更高級(jí)的并發(fā)工具替代 synchronized
- 如使用 java.util.concurrent 包中的工具類:
ReentrantLock、Semaphore、CountDownLatch、ConcurrentHashMap等; - 這些工具更靈活,有些支持超時(shí)、中斷等機(jī)制,能更好地避免死鎖。
四、總結(jié):如何查看 Java 死鎖(快速版)
| 方法 | 工具/命令 | 說明 | 是否需要重啟/侵入代碼 |
|---|---|---|---|
| jstack | 命令行工具 jstack <pid> | 查看線程 dump,自動(dòng)檢測(cè)死鎖,搜索 deadlock 關(guān)鍵字 | ? 不需要,最常用 |
| jconsole | JDK 自帶 GUI 工具 | 圖形化界面,點(diǎn)擊“檢測(cè)死鎖”按鈕 | ? 不需要 |
| VisualVM | JDK 自帶 GUI 工具 | 更強(qiáng)大的線程和內(nèi)存監(jiān)控,支持死鎖檢測(cè) | ? 不需要 |
| 編程檢測(cè) | ThreadMXBean.findDeadlockedThreads() | 可編程檢測(cè)死鎖,適合嵌入監(jiān)控系統(tǒng) | ? 需寫代碼 |
| 日志與監(jiān)控 | 結(jié)合 APM 工具(如 SkyWalking、Arthas) | 生產(chǎn)環(huán)境推薦結(jié)合工具持續(xù)監(jiān)控 | ? 可選 |
推薦做法(最佳實(shí)踐):
開發(fā)階段:
- 避免隨意嵌套 synchronized 或多個(gè)鎖;
- 使用 jstack 定期檢查線程狀態(tài),特別是在高并發(fā)測(cè)試時(shí);
- 使用工具如 jconsole / VisualVM 實(shí)時(shí)觀察線程情況。
生產(chǎn)環(huán)境:
- 通過 jstack 定時(shí)抓取線程 dump(比如通過腳本或監(jiān)控工具);
- 結(jié)合 APM 工具(如 Arthas、SkyWalking、Prometheus + Grafana)實(shí)時(shí)監(jiān)控死鎖和線程阻塞;
- 發(fā)現(xiàn)死鎖后,通過日志定位代碼,優(yōu)化鎖策略。
到此這篇關(guān)于查看java死鎖具體怎么做以及怎么避免的文章就介紹到這了,更多相關(guān)查看java死鎖及避免內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot+Redis實(shí)現(xiàn)不重復(fù)消費(fèi)的隊(duì)列的示例代碼
本文主要介紹了SpringBoot+Redis實(shí)現(xiàn)不重復(fù)消費(fèi)的隊(duì)列的示例代碼,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2024-07-07
使用Java獲取List交集數(shù)據(jù)的實(shí)現(xiàn)方案小結(jié)
今天遇到一個(gè)小需求,當(dāng)用戶上傳了一個(gè)關(guān)于用戶數(shù)據(jù)的列表,我們需要將其與數(shù)據(jù)庫中已有的用戶數(shù)據(jù)進(jìn)行比較,所以本文給大家介紹了使用Java獲取List交集數(shù)據(jù)的實(shí)現(xiàn)方案小結(jié),文中有詳細(xì)的代碼示例供大家參考,需要的朋友可以參考下2024-03-03
Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(56)
下面小編就為大家?guī)硪黄狫ava基礎(chǔ)的幾道練習(xí)題(分享)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧,希望可以幫到你2021-08-08
MyBatis-Plus中更新操作的兩種實(shí)現(xiàn)
本文主要介紹了MyBatis-Plus中更新操作的兩種實(shí)現(xiàn),主要是通過id更新和條件更新,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-04-04
Fluent Mybatis零xml配置實(shí)現(xiàn)復(fù)雜嵌套查詢
本文主要介紹了Fluent Mybatis零xml配置實(shí)現(xiàn)復(fù)雜嵌套查詢,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08
Netty源碼解析NioEventLoop創(chuàng)建的構(gòu)造方法
這篇文章主要介紹了Netty源碼解析NioEventLoopGroup之NioEventLoop創(chuàng)建的構(gòu)造方法,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
詳解java之redis篇(spring-data-redis整合)
本篇文章主要介紹了java之redis篇,主要詳細(xì)的介紹了spring-data-redis整合,有興趣的可以了解一下。2017-01-01

