实战教学的 Java 死锁案例

✅ 一、常见死锁示例代码(经典双锁互持)

public class DeadlockDemo {

    private static final Object lock1 = new Object();
    private static final Object lock2 = new Object();

    public static void main(String[] args) {

        Thread threadA = new Thread(() -> {
            synchronized (lock1) {
                System.out.println("线程A:获得 lock1,等待 lock2...");
                try {
                    Thread.sleep(100); // 模拟业务处理时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock2) {
                    System.out.println("线程A:获得 lock2");
                }
            }
        });

        Thread threadB = new Thread(() -> {
            synchronized (lock2) {
                System.out.println("线程B:获得 lock2,等待 lock1...");
                try {
                    Thread.sleep(100); // 模拟业务处理时间
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (lock1) {
                    System.out.println("线程B:获得 lock1");
                }
            }
        });

        threadA.start();
        threadB.start();
    }
}

🧨 死锁触发条件说明:

  • 线程A先获得lock1,等待lock2

  • 线程B先获得lock2,等待lock1

  • 二者互相等待,进入死锁状态


✅ 二、如何定位死锁?(详细工具与命令)

🧪 现象

  • 程序无响应/线程卡住

  • 接口调用无响应、日志长时间无输出

  • CPU占用不高,但线程数不变,且阻塞线程增加

🛠 步骤一:定位 Java 进程

jps -l
# 输出:12345 DeadlockDemo

🛠 步骤二:使用 jstack 打印线程栈

jstack 12345

🛠 步骤三:查找关键死锁提示

输出内容中查找关键词:

Found one Java-level deadlock:
"Thread-0": waiting to lock monitor 0x000..., which is held by "Thread-1"
"Thread-1": waiting to lock monitor 0x000..., which is held by "Thread-0"

🔍 你可以看到:
  • 哪些线程处于死锁

  • 哪个锁(monitor)被哪个线程持有

  • 哪个线程在等待哪个锁

🧠 补充命令说明:

命令说明
jps -l列出当前运行的 Java 进程
jstack <pid>打印线程堆栈,检测死锁
kill -3 <pid>打印线程信息到控制台(Linux)
jconsole / jvisualvm图形界面查看死锁与线程状态


✅ 三、怎么解决死锁?

🧭 原则:避免循环等待条件发生

✅ 常用解决方案:

1. 统一加锁顺序

所有线程都按 lock1 -> lock2 的顺序加锁,避免交叉等待。

2. 使用 tryLock() 加超时控制
ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

if (lock1.tryLock(1, TimeUnit.SECONDS)) {
    try {
        if (lock2.tryLock(1, TimeUnit.SECONDS)) {
            try {
                // 正常执行逻辑
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

3. 减少锁粒度 / 合并资源

如果多个资源锁可以合并为一个整体锁,降低并发复杂性

4. 死锁监控与告警机制
  • 定期 dump 线程

  • 对超时任务自动分析堆栈

  • Prometheus + AlertManager 配置线程阻塞告警


✅ 四、面试中如何回答(参考答法)

我遇到过死锁问题,通常是由于多个线程竞争多个资源,且锁获取顺序不一致造成的。在排查时,我会先通过 jps 获取 Java 进程号,然后使用 jstack 打印线程栈。死锁信息会明确指出哪些线程互相等待。我们常用的解决方式包括统一加锁顺序、使用 ReentrantLocktryLock 配合超时机制,以及在高并发系统中通过监控系统预警线程阻塞情况。在项目中,也建立了定期线程 dump 的自动化脚本以便复现问题。


✅ 五、总结:死锁排查命令清单

步骤命令说明
1jps -l获取进程号
2jstack <pid>查看线程状态
3kill -3 <pid>输出线程信息(Linux)
4jconsole/jvisualvm图形化工具辅助分析
5grep 关键词分析查找 deadlockBLOCKEDWAITING 等状态
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值