死鎖的四個必要條件:互斥、持有并等待、不可搶占、循環等待。
死鎖場景是兩個線程各自持有某個鎖,并試圖獲取對方持有的鎖,導致互相等待。
創建死鎖示例代碼
package io.renren.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;/*** 通過jstack分析線程死鎖場景*/
@RestController
public class DeadlockController {// 定義兩個互斥鎖對象private final Object lockA = new Object();private final Object lockB = new Object();@GetMapping("/deadlock")public String triggerDeadlock() {new Thread(() -> {synchronized (lockA) {System.out.println("Thread1 acquired lockA");try { Thread.sleep(100); }catch (InterruptedException e) {}synchronized (lockB) {System.out.println("Thread1 acquired lockB");}}}, "Deadlock-Thread-1").start();new Thread(() -> {synchronized (lockB) {System.out.println("Thread2 acquired lockB");try { Thread.sleep(100); }catch (InterruptedException e) {}synchronized (lockA) {System.out.println("Thread2 acquired lockA");}}}, "Deadlock-Thread-2").start();return "死鎖已觸發,檢查控制臺日志和線程狀態";}}
訪問端點觸發死鎖
http://localhost:8080/deadlock
使用 jstack 分析
查找Java進程PID
jps -l
# 輸出示例:
# 12345 com.example.DeadlockDemoApplication
生成線程轉儲
jstack -l 12345 > thread_dump.txt
分析線程轉儲:
死鎖分析圖解
解決死鎖的方案
方案1:統一鎖獲取順序
// 修改第二個線程的鎖獲取順序
new Thread(() -> {synchronized (lockA) { // 改為先獲取lockASystem.out.println("Thread2 acquired lockA");try { Thread.sleep(100); } catch (InterruptedException e) {}synchronized (lockB) {System.out.println("Thread2 acquired lockB");}}
}, "Safe-Thread-2").start();
方案2:使用 tryLock 超時機制
//定義成員變量
private final ReentrantLock lock1 = new ReentrantLock();
private final ReentrantLock lock2 = new ReentrantLock();//在函數內部執行
new Thread(() -> {try {if (lock1.tryLock(1, TimeUnit.SECONDS)) {try {System.out.println("Thread1 acquired lock1");Thread.sleep(100);if (lock2.tryLock(1, TimeUnit.SECONDS)) {try {System.out.println("Thread1 acquired lock2");} finally {lock2.unlock();}}} finally {lock1.unlock();}}} catch (InterruptedException e) {e.printStackTrace();}
}).start();
預防死鎖的最佳實踐
鎖順序:統一所有線程的鎖獲取順序
超時機制:使用 tryLock() 替代內置鎖
減少鎖粒度:避免在方法級別使用 synchronized
靜態分析工具:使用 FindBugs/SpotBugs 檢測潛在死鎖
壓力測試:使用 JMeter 模擬高并發場景
最后
實際開發中建議使用 ReentrantLock 等更靈活的工具替代 synchronized,并配合 Arthas 等在線診斷工具進行實時分析。
------------------------------------------------- jstack補充 -----------------------------------------------------
jstack命令用于打印指定Java進程、核心文件或遠程調試服務器的Java線程的Java堆棧跟蹤信息。
jstack命令可以生成JVM當前時刻的線程快照。線程快照是當前JVM內每一條線程正在執行的方法堆棧的集合,生成線程快照的主要目的是定位線程出現長時間停頓的原因,如線程間死鎖、死循環、請求外部資源導致的長時間等待等。
如果java程序崩潰生成core文件,jstack工具可以用來獲得core文件的java stack和native stack的信息,從而可以輕松地知道java程序是如何崩潰和在程序何處發生問題。
當指定的進程在64位Java虛擬機上運行時,可能需要指定-J-d64選項,例如:jstack -J-d64 -m pid。
該命令可能在未來的版本中不可用!!!