Java死锁不是会让CPU爆表吗

想获取更多高质量的Java技术文章?欢迎访问 Java技术小馆官网,持续更新优质内容,助力技术成长!

一、为什么线程会"卡死"?

上周生产环境报警群炸了------CPU使用率飙到98%!当我打开线程dump一看,二十几个线程都在BLOCKED状态,像极了早高峰的三环路。这时候老板冲过来问:"不是说死锁会卡死线程吗?怎么CPU还这么高?"

死锁的四个必要条件(交通堵塞版):

  1. 互斥条件:单行道只能过一辆车(资源独占)
  2. 请求与保持:占着左转道还想直行(持有资源不释放)
  3. 不可剥夺:没有交警强制移车(系统不能回收资源)
  4. 循环等待:四辆车十字路口互不相让(环形依赖)
csharp 复制代码
// 经典转账死锁案例
public void transfer(Account from, Account to, int amount) {
    synchronized (from) {          // 锁住转出账户
        Thread.sleep(100);        // 模拟网络延迟
        synchronized (to) {       // 尝试锁住转入账户
            from.withdraw(amount);
            to.deposit(amount);
        }
    }
}
// 当两个线程互相转账时就会死锁

二、死锁到底会不会拉高CPU?

纯死锁不会!但是...

当线程进入BLOCKED状态时,会主动让出CPU时间片。这时候CPU应该很闲才对,就像堵车时司机都熄火等待。但实际情况往往更复杂:

CPU升高的三大间接原因:

  1. 线程池的报复性补偿:大量线程被阻塞,线程池疯狂创建新线程
  2. 自旋锁的忙等待:某些锁实现会循环尝试获取锁(CAS操作)
  3. 监控系统的自救行为:健康检查、日志打印等补偿机制

最近处理的一个真实案例:某支付系统死锁后,线程池从50线程暴涨到500线程,导致CPU飙升。用jstack抓取线程快照发现,大量线程卡在ThreadPoolExecutor$Worker.run()中的getTask()调用。

三、线程的六种状态

通过jstack输出的线程状态,就能看穿JVM的内心戏:

状态 解释 CPU占用
RUNNABLE 正在执行或等待CPU时间片
BLOCKED 等待监视器锁(synchronized)
WAITING 无时限等待(Object.wait())
TIMED_WAITING 有时限等待(Thread.sleep())
TERMINATED 已终止
NEW 未启动

诊断死锁的黄金命令:

lua 复制代码
# 生成线程dump
jstack <pid> > thread_dump.log

# 查找死锁关键词
grep -A 20 "deadlock" thread_dump.log

四、CPU飙升时的破解指南

1. Arthas

arduino 复制代码
# 监控线程状态
thread -n 5

# 查看死锁
thread -b

2. VisualVM

3. 压箱底的脚本

bash 复制代码
#!/bin/bash
# 监控CPU与线程数
while true; do
    date +"%T"
    top -bn1 | grep java
    jstack <pid> | grep -E "BLOCKED|RUNNABLE" | wc -l
    sleep 2
done

五、预防死锁的六个狠招

1. 锁排序法

css 复制代码
public void transfer(Account a, Account b, int amount) {
    Account first = a.id < b.id ? a : b;
    Account second = a.id < b.id ? b : a;
    
    synchronized (first) {
        synchronized (second) {
            // 转账逻辑
        }
    }
}

2. 使用tryLock

java 复制代码
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();

if (lock1.tryLock(1, TimeUnit.SECONDS)) {
    try {
        if (lock2.tryLock(1, TimeUnit.SECONDS)) {
            try {
                // 业务逻辑
            } finally {
                lock2.unlock();
            }
        }
    } finally {
        lock1.unlock();
    }
}

3. 其他必杀技

  • 设置合理的锁超时时间
  • 避免在持锁时调用外部服务
  • 使用并发集合代替同步块
  • 定期执行死锁检测脚本

六、一个真实案例全流程复盘

背景:某电商平台大促期间,订单服务CPU持续95%+,但TPS为0。

排查过程

  1. top -Hp <pid>找到高CPU线程ID
  2. 将线程ID转十六进制:printf "%x" 114514
  3. jstack <pid> | grep -A 30 0x1bf52
  4. 发现多个线程BLOCKED在同一个锁上
  5. 检查代码发现嵌套的synchronized块
  6. 使用ReentrantLock改写并增加tryLock

优化效果

  • CPU使用率从95%降至35%
  • TPS从0恢复到1200/s
  • 99%响应时间从30s降至200ms

想获取更多高质量的Java技术文章?欢迎访问 Java技术小馆官网,持续更新优质内容,助力技术成长!

相关推荐
MaCa .BaKa4 分钟前
33-公交车司机管理系统
java·vue.js·spring boot·maven
洛小豆31 分钟前
一个场景搞明白Reachability Fence,它就像一道“结账前别走”的红外感应门
java·后端·面试
500佰33 分钟前
AI提示词(Prompt)设计优化方案 | 高效使用 AI 工具
java·人工智能·prompt·ai编程
摘星编程34 分钟前
并发设计模式实战系列(4):线程池
java·设计模式·并发编程
三原37 分钟前
前端微应用-乾坤(qiankun)原理分析-沙箱隔离(js)
前端·架构·前端框架
PGCCC1 小时前
【PGCCC】Postgres MVCC 内部:更新与插入的隐性成本
java·开发语言·数据库
诺亚凹凸曼1 小时前
Java基础系列-LinkedList源码解析
java·开发语言
爱上大树的小猪1 小时前
【前端样式】用 aspect-ratio 实现等比容器:视频封面与图片占位的终极解决方案
前端·css·面试
Maỿbe1 小时前
手动实现LinkedList
java·开发语言
江城开朗的豌豆1 小时前
CSS篇:HTML与XHTML:关键区别与实际应用解析
前端·css·面试