單步除錯會改變執行時序,掩蓋競爭條件;錯誤往往非決定性重現。核心策略:以執行緒為中心觀察,而非單步追蹤指令。
| 類型 | 徵兆 | 根因 | 預防對策 |
|---|---|---|---|
| 存取違規 | 資料損壞、異常 Crash | 多執行緒同時讀寫同一記憶體位置 | 封裝共享狀態、使用不可變物件、ThreadLocal |
| 死鎖 | 系統卡住、CPU 歸零 | 循環等待鎖(A 鎖 1 等 2,B 鎖 2 等 1) | 固定鎖獲取順序、避免持鎖睡眠、使用 tryLock(timeout) |
| 資料競爭 | 結果非決定性、資料撕裂 | 複合操作非原子(如 i++、check-then-act) | 同步區塊、AtomicInteger、ConcurrentHashMap |
| 同步開銷過大 | 吞吐率低、延遲高 | 過度同步、粗粒度鎖 | 縮小臨界區、讀寫鎖、無鎖資料結構 |
synchronized 關鍵字public void addElement(Patient p) {
synchronized (lock) { // 明確鎖物件,避免 this 洩漏
Log query = incomingPatient.getLog();
Result[] results = query.getAllSearchResults();
for (Result r : results) {
// 更新共享表格模型
setMaxColSize(table);
tableContents.add(table);
}
}
}
Thread.stop(): 強制釋放鎖導致資料不一致,Java 2 已棄用。正確做法:volatile boolean running 標誌位 + interrupt() 協作式停止。start() 僅能呼叫一次,需建立新實例。for (int i=0; i<10000; i++) {
for (int j=0; j<10000; j++) { heavyCalc(); }
Thread.yield(); // 讓同優先級執行緒獲得時間片
}
long/double 非原子寫入: 32 位 JVM 可能拆成兩次 32 位寫入,造成資料撕裂。解決:volatile 或同步區塊保護。BLOCKED、WAITING 狀態執行緒,定位死鎖鏈。多執行緒錯誤 80% 源自共享可變狀態。首選不可變設計、ThreadLocal、並發集合;必須共享時,用最小粒度同步並配合靜態分析工具(SpotBugs、Error Prone)掃描潛在競爭。除錯時「觀察執行緒狀態」勝過「單步執行指令」。