Java多线程死锁排查

【实战】Java多线程死锁排查:从复现到根治全流程

  1. 问题背景与技术原理(150字)

在Java多线程开发中,死锁是指两个或多个线程因互相持有对方所需的锁,且均不释放自身持有的锁,导致线程永久阻塞的现象。死锁需满足互斥、请求与保持、不可剥夺、循环等待四个必要条件,排查的核心是定位循环等待的锁资源与对应的线程。本文基于JDK 17环境,通过实战案例复现死锁,并讲解3种高效排查与解决方法。

  1. 死锁场景复现(附代码+架构图)

2.1 复现代码

以下代码模拟两个线程争夺两把锁,因锁获取顺序不一致导致死锁:

public class DeadLockDemo {

// 定义两把锁资源

private static final Object LOCK_A = new Object();

private static final Object LOCK_B = new Object();

public static void main(String[] args) {

// 线程1:先拿LOCK_A,再拿LOCK_B

new Thread(() -> {

synchronized (LOCK_A) {

System.out.println(Thread.currentThread().getName() + " 持有锁A,等待锁B");

try {

Thread.sleep(1000); // 模拟业务耗时,放大死锁概率

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

synchronized (LOCK_B) {

System.out.println(Thread.currentThread().getName() + " 同时持有锁A和锁B");

}

}

}, "Thread-1").start();

// 线程2:先拿LOCK_B,再拿LOCK_A

new Thread(() -> {

synchronized (LOCK_B) {

System.out.println(Thread.currentThread().getName() + " 持有锁B,等待锁A");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

synchronized (LOCK_A) {

System.out.println(Thread.currentThread().getName() + " 同时持有锁A和锁B");

}

}

}, "Thread-2").start();

}

}

2.2 运行结果

执行代码后,控制台输出如下内容,程序无后续输出且永不终止,死锁已发生:

Thread-1 持有锁A,等待锁B

Thread-2 持有锁B,等待锁A

  1. 三种死锁排查方法(从入门到进阶)

方法1:jps + jstack(JDK命令行工具,最常用)

核心原理:jps 获取Java进程ID,jstack 打印线程堆栈信息,定位死锁线程与锁资源。

  1. 打开命令行,执行 jps 命令,获取 DeadLockDemo 对应的进程ID(如 12345)。

jps

输出示例:12345 DeadLockDemo

  1. 执行 jstack 进程ID 命令,查看堆栈信息,命令行末尾会直接标记死锁:

jstack 12345

  1. 关键输出解读:

Found one Java-level deadlock:

=============================

Thread-1:

waiting to lock monitor 0x0000000003033e88 (object 0x000000076ab821c0, a java.lang.Object),

which is held by Thread-2

Thread-2:

waiting to lock monitor 0x0000000003032a48 (object 0x000000076ab821b0, a java.lang.Object),

which is held by Thread-1

输出明确显示Thread-1和Thread-2互相等待对方持有的锁,死锁定位完成。

方法2:JConsole可视化工具(直观易用,适合新手)

核心原理:JConsole是JDK自带的可视化监控工具,可实时查看线程状态,自动检测死锁。

  1. 命令行执行 jconsole 启动工具,在进程列表中选择 DeadLockDemo 进程,点击连接。

  2. 切换到线程标签页,点击右下角检测死锁按钮,工具会自动列出死锁线程及锁信息:

◦ 死锁线程:Thread-1、Thread-2

◦ 持有锁与等待锁的对应关系,与jstack输出一致。

方法3:Arthas诊断工具(进阶,适合生产环境)

核心原理:Arthas是阿里开源的Java诊断工具,支持在线排查,无需重启应用。

  1. 下载并启动Arthas,选择目标进程 DeadLockDemo。

  2. 执行 thread -b 命令,直接打印死锁线程的详细信息:

thread -b

  1. 优势:生产环境中可在线排查,支持查看线程的调用栈、CPU使用率等附加信息,排查更全面。

  2. 死锁解决方案与优化(根治思路)

方案1:统一锁获取顺序(最有效)

打破循环等待条件,所有线程按相同顺序获取锁。修改线程2的锁获取逻辑:

// 线程2修改后:先拿LOCK_A,再拿LOCK_B(与线程1顺序一致)

new Thread(() -> {

synchronized (LOCK_A) {

System.out.println(Thread.currentThread().getName() + " 持有锁A,等待锁B");

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

synchronized (LOCK_B) {

System.out.println(Thread.currentThread().getName() + " 同时持有锁A和锁B");

}

}

}, "Thread-2").start();

方案2:使用tryLock限时获取锁(灵活可控)

利用ReentrantLock的tryLock(long timeout, TimeUnit unit)方法,在指定时间内获取不到锁则放弃,打破请求与保持条件:

import java.util.concurrent.TimeUnit;

import java.util.concurrent.locks.ReentrantLock;

public class DeadLockSolveDemo {

private static final ReentrantLock LOCK_A = new ReentrantLock();

private static final ReentrantLock LOCK_B = new ReentrantLock();

public static void main(String[] args) {

new Thread(() -> {

try {

if (LOCK_A.tryLock(1, TimeUnit.SECONDS)) {

System.out.println(Thread.currentThread().getName() + " 持有锁A,等待锁B");

if (LOCK_B.tryLock(1, TimeUnit.SECONDS)) {

System.out.println(Thread.currentThread().getName() + " 同时持有锁A和锁B");

LOCK_B.unlock();

}

LOCK_A.unlock();

} else {

System.out.println(Thread.currentThread().getName() + " 获取锁A超时,放弃");

}

} catch (InterruptedException e) {

Thread.currentThread().interrupt();

}

}, "Thread-1").start();

// 线程2逻辑类似,此处省略

}

}

方案3:避免嵌套锁(从根源减少死锁概率)

简化代码逻辑,尽量减少锁的嵌套使用,降低死锁触发的可能性。

  1. 常见问题FAQ(3个高频疑问)

  2. Q:死锁和活锁的区别是什么?

A:死锁是线程互相阻塞,永不执行;活锁是线程不断尝试获取锁但失败,处于"忙碌等待"状态,程序未阻塞但无进展。

  1. Q:生产环境中如何预防死锁?

A:① 统一锁获取顺序;② 减少锁嵌套;③ 用tryLock限时获取;④ 定期监控线程状态。

  1. Q:jstack排查不到死锁怎么办?

A:确认进程ID正确;若为分布式场景,需排查多JVM实例间的资源竞争(如数据库锁、Redis锁)。

  1. 总结与延伸(互动引导)

本文通过实战案例讲解了Java多线程死锁的复现、3种排查方法及根治方案,核心是打破死锁的四个必要条件。在实际开发中,死锁排查需结合业务场景,生产环境优先使用Arthas或APM工具(如SkyWalking)进行监控。

互动问题:你在开发中遇到过哪些特殊的死锁场景?是如何排查解决的?欢迎在评论区留言讨论~

SEO标签:Java多线程、死锁排查、jstack、Arthas、并发编程

相关推荐
小小星球之旅2 小时前
CompletableFuture学习
java·开发语言·学习
jiayong233 小时前
知识库概念与核心价值01
java·人工智能·spring·知识库
皮皮林5513 小时前
告别 OOM:EasyExcel 百万数据导出最佳实践(附开箱即用增强工具类)
java
Da Da 泓4 小时前
多线程(七)【线程池】
java·开发语言·线程池·多线程
To Be Clean Coder4 小时前
【Spring源码】getBean源码实战(三)
java·mysql·spring
Wokoo74 小时前
开发者AI大模型学习与接入指南
java·人工智能·学习·架构
电摇小人5 小时前
我的“C++之旅”(博客之星主题作文)
java·开发语言
资生算法程序员_畅想家_剑魔5 小时前
Java常见技术分享-23-多线程安全-总结
java·开发语言
小李独爱秋5 小时前
计算机网络经典问题透视:常规密钥体制与公钥体制最主要的区别是什么?—— 一文带你从“钥匙”看懂现代密码学核心
服务器·网络·tcp/ip·计算机网络·密码学