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

Java 線程模型深度解析:搶佔式與協作式調度、AWT/Swing 事件線程與安全更新

     編輯:Java 相關知識

兩種線程調度模型

模型特徵Java 實現開發者責任
搶佔式OS 強制切換,時間片輪轉現代 JVM (HotSpot) 在多核預設正確同步共享資源(鎖、volatile、原子類)
協作式線程主動讓出 CPU早期 JVM、單核綠色線程適時呼叫 yield()/sleep(),避免佔用

關鍵結論:Java 規範不保證調度模型,代碼必須在兩種模型下都正確工作。

原子操作與 64 位元型別陷阱

  • 原子:≤ 32 位 變數讀寫、volatile 讀寫、引用賦值
  • 非原子:long、double(64 位)非 volatile 讀寫(可能分兩次 32 位操作)
// 危險:非原子寫入可能被讀到半舊半新值
long counter = 0; 

// 安全:volatile 或 AtomicLong
volatile long counter = 0;
AtomicLong counter = new AtomicLong(0);

協作式模型下的正確讓出

yield() 陷阱

// 錯誤:持有鎖時 yield,其他線程無法獲得鎖
synchronized (lock) {
    // 臨界區
    Thread.yield(); // 鎖未釋放!其他線程阻塞在 synchronized 外
}

// 正確:同步塊外 yield
synchronized (lock) { /* 臨界區 */ }
Thread.yield(); // 已釋放鎖

sleep() 與 wait() 差異

細粒度鎖下的 wait() 限制

// 僅釋放當前物件鎖,其他細粒度鎖仍持有
synchronized (lock1) {
    synchronized (lock2) {
        lock2.wait(); // 僅釋放 lock2,lock1 仍持有!
    }
}
// 解決:避免巢狀同步中 wait,或使用 Lock+Condition

AWT/Swing 事件分派線程 (EDT)

核心規則

  1. 所有 Swing 元件建構、修改、查詢必須在 EDT
  2. 耗時運算絕不能在 EDT,否則 UI 凍結
  3. 事件監聽器 (ActionListener 等) 在 EDT 執行,需快速返回

錯誤模式:EDT 執行耗時任務

button.addActionListener(e -> {
    // 錯誤:EDT 執行下載,UI 凍結
    File file = downloadLargeFile(url); 
    textArea.setText("完成");
});

正確模式:SwingWorker / invokeLater

button.addActionListener(e -> {
    // 正確:背景執行緒下載
    new SwingWorker() {
        @Override
        protected File doInBackground() {
            return downloadLargeFile(url); // 背景執行
        }
        @Override
        protected void done() {
            try {
                textArea.setText("完成: " + get()); // EDT 更新 UI
            } catch (Exception ex) { handleError(ex); }
        }
    }.execute();
});

非 EDT 線程安全更新 UI

// 背景執行緒計算完成,需更新 UI
// 錯誤:直接操作 Swing 元件
// label.setText("結果"); // 競態條件、繪製異常

// 正確 1:invokeLater(非阻塞)
SwingUtilities.invokeLater(() -> label.setText("結果"));

// 正確 2:invokeAndWait(阻塞等待)
try {
    SwingUtilities.invokeAndWait(() -> label.setText("結果"));
} catch (Exception e) { handleError(e); }

原理:請求放入 EDT 事件佇列,EDT 循環處理時執行 Runnable.run(),保證線程安全。

IO 與 GUI 必須多線程的原因

  • 阻塞 IO:InputStream.read() 可能無限等待,單線程會凍結整個程式
  • GUI 事件循環:需持續處理重繪、滑鼠、鍵盤事件,耗時任務會阻塞循環

總結:多線程開發鐵律

  1. 共享資源必須同步(無論調度模型)
  2. 長任務移出 EDT(SwingWorker/執行緒池)
  3. UI 更新回 EDT(invokeLater/invokeAndWait)
  4. 避免持有鎖時 sleep/yield/wait
  5. 設計前先分析共享資料、鎖粒度、線程邊界

理解 Java 線程模型的不確定性、EDT 單線程規則、同步原語正確用法,是編寫健壯併發應用的基石。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved
方法釋放鎖喚醒方式適用場景
sleep(ms)否時間到 / 中斷定時輪詢、限流延遲
wait()是notify()/中斷/超時條件等待、生產消費