Java死锁不是会让CPU爆表吗
想获取更多高质量的Java技术文章?欢迎访问 **********,持续更新优质内容,助力技术成长!
一、为什么线程会"卡死"?
上周生产环境报警群炸了——CPU使用率飙到98%!当我打开线程dump一看,二十几个线程都在BLOCKED
状态,像极了早高峰的三环路。这时候老板冲过来问:"不是说死锁会卡死线程吗?怎么CPU还这么高?"
死锁的四个必要条件(交通堵塞版):
- 互斥条件:单行道只能过一辆车(资源独占)
- 请求与保持:占着左转道还想直行(持有资源不释放)
- 不可剥夺:没有交警强制移车(系统不能回收资源)
- 循环等待:四辆车十字路口互不相让(环形依赖)
// 经典转账死锁案例 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升高的三大间接原因:
- 线程池的报复性补偿:大量线程被阻塞,线程池疯狂创建新线程
- 自旋锁的忙等待:某些锁实现会循环尝试获取锁(CAS操作)
- 监控系统的自救行为:健康检查、日志打印等补偿机制
最近处理的一个真实案例:某支付系统死锁后,线程池从50线程暴涨到500线程,导致CPU飙升。用jstack
抓取线程快照发现,大量线程卡在ThreadPoolExecutor$Worker.run()
中的getTask()
调用。
三、线程的六种状态
通过jstack
输出的线程状态,就能看穿JVM的内心戏:
状态 | 解释 | CPU占用 |
RUNNABLE | 正在执行或等待CPU时间片 | 高 |
BLOCKED | 等待监视器锁(synchronized) | 无 |
WAITING | 无时限等待(Object.wait()) | 无 |
TIMED_WAITING | 有时限等待(Thread.sleep()) | 无 |
TERMINATED | 已终止 | 无 |
NEW | 未启动 | 无 |
诊断死锁的黄金命令:
# 生成线程dump jstack <pid> > thread_dump.log # 查找死锁关键词 grep -A 20 "deadlock" thread_dump.log
四、CPU飙升时的破解指南
1. Arthas
# 监控线程状态 thread -n 5 # 查看死锁 thread -b
2. VisualVM
3. 压箱底的脚本
#!/bin/bash # 监控CPU与线程数 while true; do date +"%T" top -bn1 | grep java jstack <pid> | grep -E "BLOCKED|RUNNABLE" | wc -l sleep 2 done
五、预防死锁的六个狠招
1. 锁排序法
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
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。
排查过程:
top -Hp <pid>
找到高CPU线程ID- 将线程ID转十六进制:
printf "%x" 114514
jstack <pid> | grep -A 30 0x1bf52
- 发现多个线程BLOCKED在同一个锁上
- 检查代码发现嵌套的synchronized块
- 使用ReentrantLock改写并增加tryLock
优化效果:
- CPU使用率从95%降至35%
- TPS从0恢复到1200/s
- 99%响应时间从30s降至200ms
想获取更多高质量的Java技术文章?欢迎访问 **********,持续更新优质内容,助力技术成长!
#java##线程#