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

《Filthy Rich Clients》讀書筆記(一):Swing EDT 事件分發執行緒深度解析

     編輯:Java 相關知識

Swing 作為單執行緒圖形工具包,其核心機制——事件分發執行緒(Event Dispatch Thread,EDT)決定了介面的響應性與線程安全。許多開發者將耗時業務邏輯直接寫在事件監聽器中,導致介面凍結,誤以為是 Swing 效能問題。本文系統梳理 EDT 運作原理、同步/非同步機制、invokeLater/invokeAndWait 正確用法,並對比生產者/消費者模型,幫助開發者建立正確的執行緒認知。

一、核心概念釐清

同步 vs 非同步

  • 同步:發起請求後阻塞等待結果(如 invokeAndWait)。
  • 非同步:發起請求立即返回,稍後處理(如 invokeLater、事件發佈)。

串行 vs 並行

  • 串行:任務依序執行,EDT 單執行緒天然串行處理事件佇列。
  • 並行:多執行緒同時推進,實為時間片輪換模擬。

生產者/消費者模型

事件佇列即「傳送帶」:

  • 生產者:各類事件來源(滑鼠、鍵盤、invokeLater、定時器、網路回呼)。
  • 消費者:唯一的 EDT,循環 take() → dispatch()。
  • 同步隊列實作:以 wait()/notifyAll() 協調生產與消費速率。
public class SyncQueue {
    private final List queue = new LinkedList<>();
    private final Object LOCK = new Object();

    public T pop() {
        synchronized (LOCK) {
            while (queue.isEmpty()) {
                try { LOCK.wait(); } catch (InterruptedException ignored) {}
            }
            return queue.remove(0);
        }
    }
    public void push(T e) {
        synchronized (LOCK) {
            queue.add(e);
            LOCK.notifyAll();
        }
    }
}

二、Swing 事件佇列與 EDT

EDT 為單一消費者,終生循環於「等待事件 → 分發處理」。兩大特性:

  1. 將同步操作轉為非同步:invokeLater 讓呼叫執行緒立即返回。
  2. 將並行存取序列化:所有 GUI 操作強制串行進入 EDT,避免競態條件。

鐵律:

  • 所有 GUI 請求必須在 EDT 執行(SwingUtilities.isEventDispatchThread() 驗證)。
  • 非 GUI 耗時操作(I/O、運算、資料庫)嚴禁佔用 EDT,需放入工作執行緒(SwingWorker、線程池)。

三、invokeLater 與 invokeAndWait

方法模式返回時機典型用途
invokeLater(Runnable)非同步立即返回,不等待執行完成UI 狀態更新、日誌記錄、觸發重繪
invokeAndWait(Runnable)同步阻塞呼叫執行緒,直到 EDT 執行完畢從 UI 元件安全讀取資料(如 JSlider.getValue())

invokeAndWait 內部機制與死鎖防護

public static void invokeAndWait(Runnable r) throws InterruptedException, InvocationTargetException {
    if (EventQueue.isDispatchThread()) {
        throw new Error("Cannot call invokeAndWait from the event dispatcher thread");
    }
    Object lock = new Object(); // 專用鎖
    InvocationEvent event = new InvocationEvent(Toolkit.getDefaultToolkit(), r, lock, true);
    synchronized (lock) {
        Toolkit.getEventQueue().postEvent(event); // 入隊
        lock.wait(); // 阻塞當前執行緒
    }
    // 檢查異常傳播...
}

為何禁止在 EDT 呼叫? 若在 EDT 內調用,會將事件入隊後 wait() 釋放鎖,但 EDT 自身被阻塞,永無法從佇列取出該事件執行 → 經典死鎖(雙向等待)。Swing 故意拋出 Error 阻斷此誤用。

四、paintImmediately 的特殊性

繞過佇列順序,直接在當前繪製請求中插入優先繪製,屬於「破壞 FIFO」的緊急繪製手段,慎用。

五、SwingWorker 最佳實踐

JDK 6+ 內建 SwingWorker 封裝背景任務與 EDT 互動:

SwingWorker worker = new SwingWorker<>() {
    @Override protected Void doInBackground() {
        // 耗時運算,定期 publish(進度)
        return null;
    }
    @Override protected void process(List chunks) {
        // EDT 更新進度條
    }
    @Override protected void done() {
        // EDT 完成後處理
    }
};
worker.execute();

推薦參考陳維雷博客《SwingWorker 詳解》三部曲,涵蓋取消、進度、異常處理等進階主題。

六、EDT 對設計模式的啟示

單執行緒存取模式可推廣至任何非線程安全庫:將共享資源存取限制在單一執行緒,即可免除鎖開銷與同步複雜度。桌面端請求量遠小於伺服端,單執行緒序列化足以應對,保證了事件順序的可預測性——這正是 Swing 選擇單執行緒模型的架構智慧。

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