news 2026/9/3 3:29:43

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
【Java】死锁排查与预防:jstack与ReentrantLock.tryLock详解

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快速定位、通过并发工具减少锁依赖,三管齐下才能构建健壮的并发系统。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/2 5:19:33

创业公司低成本启动方案:用ms-swift快速验证产品原型

创业公司低成本启动方案&#xff1a;用ms-swift快速验证产品原型 在AI原生应用的浪潮中&#xff0c;越来越多创业团队试图以大模型为核心构建创新产品。然而现实往往是残酷的——设想中的智能客服、个性化推荐或自动写作助手&#xff0c;在落地时却卡在了训练环境搭建、显存不足…

作者头像 李华
网站建设 2026/9/2 21:00:59

ms-swift支持远程协作白板内容生成

ms-swift赋能远程协作白板的智能生成 在远程办公日益普及的今天&#xff0c;团队协作早已从简单的文字沟通转向更直观的视觉表达。白板工具如Miro、Figma Whiteboard或腾讯文档脑图&#xff0c;已成为产品设计、会议讨论和教学讲解的核心载体。然而&#xff0c;一个普遍痛点也随…

作者头像 李华
网站建设 2026/9/2 14:18:59

AI识别新姿势:用预训练模型快速实现中文场景理解

AI识别新姿势&#xff1a;用预训练模型快速实现中文场景理解 作为一名每天需要审核数千张用户上传图片的内容审核员&#xff0c;手动筛查不仅效率低下&#xff0c;还容易因疲劳导致误判。本文将介绍如何利用预训练的大模型快速搭建一套中文场景理解系统&#xff0c;帮助你自动识…

作者头像 李华
网站建设 2026/9/2 11:15:52

【独家技术揭秘】:大厂都在用的VSCode智能体测试架构设计

第一章&#xff1a;VSCode自定义智能体测试架构概述在现代软件开发流程中&#xff0c;集成开发环境&#xff08;IDE&#xff09;的智能化程度直接影响开发效率与代码质量。VSCode 作为广受欢迎的轻量级编辑器&#xff0c;通过其强大的扩展机制支持构建自定义智能体测试架构&…

作者头像 李华
网站建设 2026/9/2 23:56:17

多模态识别探索:图文匹配模型的快速实验环境

多模态识别探索&#xff1a;图文匹配模型的快速实验环境 如果你正在研究多模态识别技术&#xff0c;尤其是图文匹配模型&#xff0c;那么配置开发环境可能会让你头疼。复杂的依赖关系、CUDA版本冲突、模型权重下载等问题常常会消耗大量时间。本文将介绍如何利用预置的"多模…

作者头像 李华
网站建设 2026/9/2 21:36:08

网盘直链下载助手集成Qwen3Guard-Gen-8B防范非法文件传播

网盘直链下载助手集成Qwen3Guard-Gen-8B防范非法文件传播 在生成式AI迅速渗透各类应用场景的今天&#xff0c;一个看似简单的功能——“帮我找某个资源”——可能暗藏巨大风险。尤其是在网盘直链下载助手中&#xff0c;用户通过自然语言请求获取影视、软件或文档资源时&#xf…

作者头像 李华