广安市网站建设_网站建设公司_Linux_seo优化
2026/1/7 9:27:25 网站建设 项目流程

Java死锁排查与预防:jstack与ReentrantLock.tryLock详解

一、死锁基础回顾

1.1 死锁四要素(Coffman条件)

死锁的发生必须同时满足以下四个条件:

  1. 互斥条件:资源只能被一个线程独占
  2. 请求与保持:线程持有资源A的同时请求资源B
  3. 不剥夺:已获得的资源不能被强制剥夺
  4. 循环等待:存在线程资源的环形等待链
// 经典死锁图示Thread-1:lockA.lock()→ 请求 lockBThread-2:lockB.lock()→ 请求 lockA ╭─────────────╮ │ 循环等待 │ ╰─────────────╯

二、手写一个可复现的死锁

2.1 完整死锁示例代码

publicclassDeadlockDemo{// 两个共享资源privatestaticfinalObjectlockA=newObject();privatestaticfinalObjectlockB=newObject();publicstaticvoidmain(String[]args){// 线程1:先获取lockA,再请求lockBThreadt1=newThread(()->{synchronized(lockA){System.out.println("Thread-1: 持有 lockA");try{Thread.sleep(100);}catch(InterruptedExceptione){}// 确保线程2先拿到lockBSystem.out.println("Thread-1: 等待 lockB...");synchronized(lockB){System.out.println("Thread-1: 获取了 lockB");}}},"Thread-1");// 线程2:先获取lockB,再请求lockAThreadt2=newThread(()->{synchronized(lockB){System.out.println("Thread-2: 持有 lockB");try{Thread.sleep(100);}catch(InterruptedExceptione){}System.out.println("Thread-2: 等待 lockA...");synchronized(lockA){System.out.println("Thread-2: 获取了 lockA");}}},"Thread-2");t1.start();t2.start();// 等待线程执行(程序将永远卡死)try{t1.join();t2.join();}catch(InterruptedExceptione){e.printStackTrace();}}}

运行这个程序,控制台输出

Thread-1: 持有 lockA Thread-2: 持有 lockB Thread-1: 等待 lockB... Thread-2: 等待 lockA... [程序卡死]

三、jstack死锁分析实战

3.1 jstack工具介绍

jstack是JDK自带的线程堆栈分析工具,可以快速定位死锁

# 查找Java进程IDjps -l# 对目标进程执行jstackjstack<pid>>deadlock.log# 或实时查看jstack -l<pid>

3.2 分析死锁日志

对上面的死锁程序执行jstack后,关键部分如下:

Found one Java-level deadlock: ============================= "Thread-2": waiting to lock monitor 0x0000000018f0e800 (object 0x00000000d6e0a000, a java.lang.Object), which is held by "Thread-1" "Thread-1": waiting to lock monitor 0x0000000018f0d800 (object 0x00000000d6e0a010, a java.lang.Object), which is held by "Thread-2" Java stack information for the threads listed above: =================================================== "Thread-2": at DeadlockDemo.lambda$main$1(DeadlockDemo.java:28) - waiting to lock <0x00000000d6e0a000> (a java.lang.Object) ← 等待lockA - locked <0x00000000d6e0a010> (a java.lang.Object) ← 已持有lockB at java.lang.Thread.run(Thread.java:748) "Thread-1": at DeadlockDemo.lambda$main$0(DeadlockDemo.java:15) - waiting to lock <0x00000000d6e0a010> (a java.lang.Object) ← 等待lockB - locked <0x00000000d6e0a000> (a java.lang.Object) ← 已持有lockA at java.lang.Thread.run(Thread.java:748) Found 1 deadlock.

3.3 日志解读要点

  1. Found one Java-level deadlock: 明确检测到死锁
  2. Thread-2 waiting to lock… held by Thread-1: 清晰的等待关系
  3. 十六进制地址:0x00000000d6e0a000对应代码中的lockA对象
  4. 线程栈: 精确到死锁发生的代码行号(DeadlockDemo.java:28)

定位技巧

  • - locked:该线程已持有的锁
  • - waiting to lock:该线程正在等待的锁
  • 通过对象地址匹配,画出死锁环

四、ReentrantLock.tryLock(timeout)避免死锁

4.1 tryLock机制原理

publicclassSafeLockDemo{privatestaticfinalReentrantLocklockA=newReentrantLock();privatestaticfinalReentrantLocklockB=newReentrantLock();publicstaticvoidmain(String[]args){// 线程1:尝试获取,失败则超时释放Threadt1=newThread(()->{try{if(lockA.tryLock(100,TimeUnit.MILLISECONDS)){try{System.out.println("Thread-1: 持有 lockA");Thread.sleep(50);System.out.println("Thread-1: 尝试获取 lockB...");if(lockB.tryLock(100,TimeUnit.MILLISECONDS)){try{System.out.println("Thread-1: 成功获取 lockB");}finally{lockB.unlock();}}else{System.out.println("Thread-1: 获取 lockB 超时,释放 lockA");}}finally{lockA.unlock();}}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}},"Thread-1");// 线程2:同逻辑Threadt2=newThread(()->{try{if(lockB.tryLock(100,TimeUnit.MILLISECONDS)){try{System.out.println("Thread-2: 持有 lockB");Thread.sleep(50);System.out.println("Thread-2: 尝试获取 lockA...");if(lockA.tryLock(100,TimeUnit.MILLISECONDS)){try{System.out.println("Thread-2: 成功获取 lockA");}finally{lockA.unlock();}}else{System.out.println("Thread-2: 获取 lockA 超时,释放 lockB");}}finally{lockB.unlock();}}}catch(InterruptedExceptione){Thread.currentThread().interrupt();}},"Thread-2");t1.start();t2.start();}}

运行输出(不死锁):

Thread-1: 持有 lockA Thread-2: 持有 lockB Thread-1: 尝试获取 lockB... Thread-2: 尝试获取 lockA... Thread-1: 获取 lockB 超时,释放 lockA Thread-2: 获取 lockA 超时,释放 lockB [程序正常结束]

4.2 tryLock核心优势

打破死锁的"不剥夺"和"循环等待"条件

  1. 超时机制:不再无限等待,主动放弃
  2. 响应中断:可被interrupt()唤醒
  3. 公平性选择:支持公平锁,减少饥饿
// 带公平锁的创建方式privatestaticfinalReentrantLockfairLockA=newReentrantLock(true);

4.3 最佳实践模式

模式1:限时获取多锁

publicbooleantransfer(Accountfrom,Accountto,doubleamount,longtimeout){longstopTime=System.nanoTime()+timeout;while(System.nanoTime()<stopTime){if(from.lock.tryLock()){try{if(to.lock.tryLock()){try{if(from.balance>=amount){from.balance-=amount;to.balance+=amount;returntrue;}}finally{to.lock.unlock();}}}finally{from.lock.unlock();}}// 退避策略,避免活锁Thread.yield();}returnfalse;// 超时失败}

模式2:固定顺序获取锁

// 通过对象ID排序,确保所有线程按相同顺序获取锁privatevoidsafeLock(Accounta1,Accounta2){if(a1.getId()<a2.getId()){synchronized(a1){synchronized(a2){/* 操作 */}}}else{synchronized(a2){synchronized(a1){/* 操作 */}}}}

五、其他死锁避免策略

5.1 使用并发工具类

ConcurrentHashMap替代手动锁Map:

// ❌ 手动加锁,易死锁synchronized(map){map.put(key,value);}// ✅ 无锁化操作map.putIfAbsent(key,value);

5.2 死锁检测工具

Arthas在线诊断(比jstack更强大):

# 安装Arthascurl-O https://arthas.aliyun.com/arthas-boot.jar# 启动并attach到进程java -jar arthas-boot.jar# 一键检测死锁[arthas]$ thread -b

输出示例

"thread-2" Id=13 BLOCKED on java.lang.Object@7d4991ad owned by "thread-1" Id=12 "thread-1" Id=12 BLOCKED on java.lang.Object@16b4a017 owned by "thread-2" Id=13 [arthas]$ thread 12 # 查看线程12详细堆栈

5.3 代码规范预防

避免嵌套锁

// ❌ 高风险publicvoidmethodA(){synchronized(lock1){methodB();// 内部可能获取lock2}}// ✅ 重构为无锁publicvoidmethodA(){// 预先获取所有需要的数据Datadata=copyData();methodB(data);// 无锁操作}

锁粒度最小化

// ❌ 粗粒度锁synchronized(bigLock){// 大量操作}// ✅ 细粒度锁Objectdata=getData();synchronized(dataLock){update(data);// 只保护关键部分}saveToDB(data);

六、总结:死锁排查与预防清单

6.1 死锁排查三步法

  1. 现象确认:应用卡死、CPU使用率不高、线程状态BLOCKED
  2. jstack定位jstack pid > log.txt,搜索"deadlock"
  3. 日志分析:识别waiting to locklocked的循环关系

6.2 死锁预防四原则

原则实践方案工具/代码示例
互斥→共享使用并发容器ConcurrentHashMap
请求保持→一次性批量获取锁tryLock(timeout)
不剥夺→可中断锁可被超时释放ReentrantLock.tryLock()
循环等待→顺序化按固定顺序加锁if (id1 < id2) { lock1.lock(); lock2.lock(); }

6.3 选型决策树

是否需要锁? ├─ 否 → 无锁设计(CAS、并发容器) └─ 是 → 是否需要重入? ├─ 否 → synchronized └─ 是 → 是否需要超时/中断? ├─ 否 → ReentrantLock └─ 是 → tryLock(timeout) ⭐

金句:死锁是并发编程的"癌症",预防胜于治疗。通过tryLock打破无限等待、通过jstack快速定位、通过并发工具减少锁依赖,三管齐下才能构建健壮的并发系统。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询