news 2026/6/24 23:53:57

【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/6/13 0:38:11

量化配置向导:选择合适的比特数与算法实现最优平衡

量化配置向导&#xff1a;选择合适的比特数与算法实现最优平衡 在大模型落地日益加速的今天&#xff0c;一个现实问题摆在每一位工程师面前&#xff1a;如何让拥有数十亿参数的庞然大物&#xff0c;在有限显存和算力资源下依然高效运行&#xff1f;FP16精度下的Qwen3-7B模型光权…

作者头像 李华
网站建设 2026/6/15 16:12:59

告别环境配置:云端GPU+预置镜像实现万物识别

告别环境配置&#xff1a;云端GPU预置镜像实现万物识别 作为一名独立开发者&#xff0c;我最近在为智能相册应用添加物品识别功能时遇到了难题&#xff1a;本地电脑性能不足&#xff0c;又不想花费大量时间配置复杂的深度学习环境。经过实践&#xff0c;我发现使用云端GPU配合预…

作者头像 李华
网站建设 2026/6/20 6:06:36

万物识别模型调优指南:从预置镜像到生产部署

万物识别模型调优指南&#xff1a;从预置镜像到生产部署 在AI技术快速发展的今天&#xff0c;万物识别模型已经成为许多业务场景中的关键组件。无论是电商平台的商品识别、智慧城市的安防监控&#xff0c;还是教育领域的科普应用&#xff0c;都需要稳定可靠的识别能力。本文将分…

作者头像 李华
网站建设 2026/6/17 23:51:06

零基础玩转AI万物识别:10分钟搭建中文通用识别模型

零基础玩转AI万物识别&#xff1a;10分钟搭建中文通用识别模型 作为一名电商创业者&#xff0c;你是否遇到过这样的困扰&#xff1a;每天需要手动分类大量商品图片&#xff0c;耗时耗力还容易出错&#xff1f;深度学习听起来高大上&#xff0c;但环境配置和模型训练的门槛让人望…

作者头像 李华
网站建设 2026/6/13 16:06:15

识别模型微调实战:基于预训练模型的快速适配

识别模型微调实战&#xff1a;基于预训练模型的快速适配 如果你是一位领域专家&#xff0c;手头有一批专业图像数据&#xff0c;想要基于通用识别模型进行领域适配&#xff0c;但缺乏深度学习工程经验&#xff0c;那么这篇文章就是为你准备的。本文将带你快速上手如何使用预训练…

作者头像 李华
网站建设 2026/6/23 16:22:33

万物识别API开发全攻略:从环境搭建到服务部署

万物识别API开发全攻略&#xff1a;从环境搭建到服务部署 作为一名全栈工程师&#xff0c;最近我接到了开发自定义识别API的任务。虽然对后端开发轻车熟路&#xff0c;但深度学习环境搭建却让我犯了难。经过实践&#xff0c;我总结出这套完整的开发指南&#xff0c;帮助同样需…

作者头像 李华