首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java 執行緒死結完整解析:原因、預防與 stop/suspend 棄用替代方案

     編輯:Java 相關知識

死結成因

執行緒可能進入阻塞,物件可能有「同步」方法——除非同步鎖被釋放,否則執行緒無法存取該物件。一個執行緒等待另一物件,另一物件等待下一物件……最可怕形成**封閉等待鏈**——最後物件等待第一個!所有執行緒陷入無休止相互等待,動彈不得。此即死結。雖不常見,一旦碰到除錯極其艱難。語言本身未直接提供預防幫助,需謹慎設計避免。

Java 1.2 棄用 stop/suspend/resume/destroy

為減少死結可能,Java 1.2 「反對」使用這四個方法。

為何棄用 stop()?

不安全。它會解除執行緒獲得的所有鎖定,若物件處於不連貫狀態(被破壞),其他執行緒能在該狀態下檢查修改。造成微妙局面,難檢查真正問題。**應避免 stop()**,採用 Blocking.java 方式:用標誌告知執行緒何時透過退出 run() 中止。

阻塞執行緒的正確中止:interrupt()

若執行緒阻塞(如等待輸入),無法像 Blocking.java 輪詢標誌。但仍不該用 stop(),而應用 Thread.interrupt() 中止並退出阻塞代碼。

//: Interrupt.java
class Blocked extends Thread {
    public synchronized void run() {
        try { wait(); } catch(InterruptedException e) { System.out.println("InterruptedException"); }
        System.out.println("Exiting run()");
    }
}

public class Interrupt extends Applet {
    private Button interrupt = new Button("Interrupt");
    private Blocked blocked = new Blocked();
    public void init() {
        add(interrupt);
        interrupt.addActionListener(e -> {
            System.out.println("Button pressed");
            if(blocked == null) return;
            Thread remove = blocked;
            blocked = null; // 釋放給 GC
            remove.interrupt(); // 關鍵:中斷阻塞
        });
        blocked.start();
    }
} ///:~

wait() 產生阻塞。按下按鈕將參考設 null 釋放 GC,呼叫 interrupt()。首次按下見執行緒正常退出;之後無執行緒可「殺」只見按鈕被按下。

suspend()/resume() 天生易死結

呼叫 suspend() 目標執行緒停下但**仍持有之前獲得的鎖定**。此時其他執行緒無法存取鎖定資源,除非被掛起執行緒恢復。若任何執行緒想恢復目標執行緒同時又試圖使用鎖定資源 → **死結**。

**正確替代方案:** 在 Thread 類中放入標誌指示活動/掛起。標誌指示掛起 → 用 wait() 進入等待。指示恢復 → 用 notify() 重啟。

Suspend.java:正確掛起/恢復實作

//: Suspend.java
public class Suspend extends Applet {
    private TextField t = new TextField(10);
    private Button suspend = new Button("Suspend"), resume = new Button("Resume");
    
    class Suspendable extends Thread {
        private int count = 0;
        private boolean suspended = false;
        public Suspendable() { start(); }
        public void fauxSuspend() { suspended = true; }
        public synchronized void fauxResume() { suspended = false; notify(); }
        
        public void run() {
            while (true) {
                try {
                    sleep(100);
                    synchronized(this) { while(suspended) wait(); }
                } catch (InterruptedException e) {}
                t.setText(Integer.toString(count++));
            }
        }
    }
    
    private Suspendable ss = new Suspendable();
    public void init() {
        add(t);
        suspend.addActionListener(e -> ss.fauxSuspend());
        add(suspend);
        resume.addActionListener(e -> ss.fauxResume());
        add(resume);
    }
} ///:~

關鍵設計要點

  • suspended 標誌開關掛起狀態
  • run() 中 synchronized(this) { while(suspended) wait(); } —— wait() 必須同步以使用物件鎖
  • fauxResume() 設 false 並 notify() —— 同步方法確保呼叫 notify() 前取得物件鎖(喚醒的 wait() 使用)
  • 遵循此模式可避免 wait()/notify() 陷阱

destroy() 從未實作

類似無法恢復的 suspend(),有相同死結問題。未明確「反對」,或許未來版本(1.2 後)實作,用於可承受死結風險的特殊場合。

為何當初實作這些現在棄用的方法?

Sun 讓技術人員決定語言改動而非行銷人員。技術人員更理解語言本質。犯錯後能理智正視。Java 能持續進步,即使讓程式設計師多少不便。寧願面對不便,不願語言停滯不前。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved