Java死鎖不是會讓CPU爆表嗎
想獲取更多高質(zhì)量的Java技術(shù)文章?歡迎訪問 **********,持續(xù)更新優(yōu)質(zhì)內(nèi)容,助力技術(shù)成長!
一、為什么線程會"卡死"?
上周生產(chǎn)環(huán)境報警群炸了——CPU使用率飆到98%!當我打開線程dump一看,二十幾個線程都在BLOCKED
狀態(tài),像極了早高峰的三環(huán)路。這時候老板沖過來問:"不是說死鎖會卡死線程嗎?怎么CPU還這么高?"
死鎖的四個必要條件(交通堵塞版):
- 互斥條件:單行道只能過一輛車(資源獨占)
- 請求與保持:占著左轉(zhuǎn)道還想直行(持有資源不釋放)
- 不可剝奪:沒有交警強制移車(系統(tǒng)不能回收資源)
- 循環(huán)等待:四輛車十字路口互不相讓(環(huán)形依賴)
// 經(jīng)典轉(zhuǎn)賬死鎖案例 public void transfer(Account from, Account to, int amount) { synchronized (from) { // 鎖住轉(zhuǎn)出賬戶 Thread.sleep(100); // 模擬網(wǎng)絡(luò)延遲 synchronized (to) { // 嘗試鎖住轉(zhuǎn)入賬戶 from.withdraw(amount); to.deposit(amount); } } } // 當兩個線程互相轉(zhuǎn)賬時就會死鎖
二、死鎖到底會不會拉高CPU?
純死鎖不會!但是...
當線程進入BLOCKED
狀態(tài)時,會主動讓出CPU時間片。這時候CPU應(yīng)該很閑才對,就像堵車時司機都熄火等待。但實際情況往往更復(fù)雜:
CPU升高的三大間接原因:
- 線程池的報復(fù)性補償:大量線程被阻塞,線程池瘋狂創(chuàng)建新線程
- 自旋鎖的忙等待:某些鎖實現(xiàn)會循環(huán)嘗試獲取鎖(CAS操作)
- 監(jiān)控系統(tǒng)的自救行為:健康檢查、日志打印等補償機制
最近處理的一個真實案例:某支付系統(tǒng)死鎖后,線程池從50線程暴漲到500線程,導(dǎo)致CPU飆升。用jstack
抓取線程快照發(fā)現(xiàn),大量線程卡在ThreadPoolExecutor$Worker.run()
中的getTask()
調(diào)用。
三、線程的六種狀態(tài)
通過jstack
輸出的線程狀態(tài),就能看穿JVM的內(nèi)心戲:
狀態(tài) | 解釋 | CPU占用 |
RUNNABLE | 正在執(zhí)行或等待CPU時間片 | 高 |
BLOCKED | 等待監(jiān)視器鎖(synchronized) | 無 |
WAITING | 無時限等待(Object.wait()) | 無 |
TIMED_WAITING | 有時限等待(Thread.sleep()) | 無 |
TERMINATED | 已終止 | 無 |
NEW | 未啟動 | 無 |
診斷死鎖的黃金命令:
# 生成線程dump jstack <pid> > thread_dump.log # 查找死鎖關(guān)鍵詞 grep -A 20 "deadlock" thread_dump.log
四、CPU飆升時的破解指南
1. Arthas
# 監(jiān)控線程狀態(tài) thread -n 5 # 查看死鎖 thread -b
2. VisualVM
3. 壓箱底的腳本
#!/bin/bash # 監(jiān)控CPU與線程數(shù) while true; do date +"%T" top -bn1 | grep java jstack <pid> | grep -E "BLOCKED|RUNNABLE" | wc -l sleep 2 done
五、預(yù)防死鎖的六個狠招
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) { // 轉(zhuǎn)賬邏輯 } } }
2. 使用tryLock
Lock lock1 = new ReentrantLock(); Lock lock2 = new ReentrantLock(); if (lock1.tryLock(1, TimeUnit.SECONDS)) { try { if (lock2.tryLock(1, TimeUnit.SECONDS)) { try { // 業(yè)務(wù)邏輯 } finally { lock2.unlock(); } } } finally { lock1.unlock(); } }
3. 其他必殺技
- 設(shè)置合理的鎖超時時間
- 避免在持鎖時調(diào)用外部服務(wù)
- 使用并發(fā)集合代替同步塊
- 定期執(zhí)行死鎖檢測腳本
六、一個真實案例全流程復(fù)盤
背景:某電商平臺大促期間,訂單服務(wù)CPU持續(xù)95%+,但TPS為0。
排查過程:
top -Hp <pid>
找到高CPU線程ID- 將線程ID轉(zhuǎn)十六進制:
printf "%x" 114514
jstack <pid> | grep -A 30 0x1bf52
- 發(fā)現(xiàn)多個線程BLOCKED在同一個鎖上
- 檢查代碼發(fā)現(xiàn)嵌套的synchronized塊
- 使用ReentrantLock改寫并增加tryLock
優(yōu)化效果:
- CPU使用率從95%降至35%
- TPS從0恢復(fù)到1200/s
- 99%響應(yīng)時間從30s降至200ms
想獲取更多高質(zhì)量的Java技術(shù)文章?歡迎訪問 **********,持續(xù)更新優(yōu)質(zhì)內(nèi)容,助力技術(shù)成長!
#java##線程#