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

SwingUtilities.invokeLater 與 invokeAndWait 深度解析:線程安全 GUI 更新指南

     編輯:Java 相關知識

核心原則:Swing 單線程模型

Swing 線程不安全,採單線程設計:只能從事件派發線程(EDT)訪問將在屏幕繪製的 Swing 組件。EDT 調用 paint、update 等回調,也執行事件監聽器方法(如 ActionListener.actionPerformed)。Swing 事件驅動,在回調中更新 GUI 很自然。但若在 actionPerformed 執行耗時操作,EDT 會阻塞,界面假死,按鈕長時間保持按下狀態。此時應在獨立線程執行耗時任務,再通過 SwingUtilities 將 GUI 更新提交給 EDT。

錯誤示範:非 EDT 直接更新組件

startButton.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent e) {
        GetInfoThread t = new GetInfoThread(Test.this);
        t.start();
        startButton.setEnabled(false); // EDT 執行,合法
    }
});
class GetInfoThread extends Thread {
    Test applet;
    public GetInfoThread(Test applet) { this.applet = applet; }
    public void run() {
        while (true) {
            try { Thread.sleep(500); applet.getProgressBar().setValue(Math.random() * 100); }
            catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
}

錯誤分析:GetInfoThread 中直接設置進度條,屬非 EDT 更新組件,運行不正常。

正確用法一:invokeLater(非阻塞)

class GetInfoThread extends Thread {
    Test applet;
    Runnable runx;
    int value;
    public GetInfoThread(final Test applet) {
        this.applet = applet;
        runx = new Runnable() {
            public void run() {
                JProgressBar jpb = applet.getProgressBar();
                jpb.setValue(value);
            }
        };
    }
    public void run() {
        while (true) {
            try {
                Thread.sleep(500);
                value = (int) (Math.random() * 100);
                SwingUtilities.invokeLater(runx); // 提交給 EDT,立即返回
            } catch (InterruptedException e) { e.printStackTrace(); }
        }
    }
}

invokeLater 將 Runnable 排入 EDT 隊列,隨後立即返回,不等待執行完成。

正確用法二:invokeAndWait(阻塞等待)

class GetInfoThread extends Thread {
    Runnable getValue, setValue;
    int value, currentValue;
    public GetInfoThread(final Test applet) {
        getValue = new Runnable() {
            public void run() {
                JProgressBar pb = applet.getProgressBar();
                currentValue = pb.getValue();
            }
        };
        setValue = new Runnable() {
            public void run() {
                JProgressBar pb = applet.getProgressBar();
                pb.setValue(value);
            }
        };
    }
    public void run() {
        while (true) {
            try {
                Thread.sleep(500);
                value = (int) (Math.random() * 100);
                try { SwingUtilities.invokeAndWait(getValue); } // 阻塞直到 getValue 執行完
                catch (Exception ex) {}
                if (currentValue != value) { SwingUtilities.invokeLater(setValue); }
            } catch (Exception ex) {}
        }
    }
}

invokeAndWait 同樣排入 EDT 隊列,但阻塞調用線程直到 run() 執行完畢。適用於需從組件獲取信息後才能執行後續操作的場景。

關鍵區別與死鎖警示

  • invokeLater 可在 EDT 中調用;invokeAndWait 絕不可在 EDT 中調用,否則會死鎖:EDT 等待 invokeAndWait 返回才能派發事件,而 invokeAndWait 又等待 EDT 派發其 Runnable。
  • 例:在 actionPerformed 中調用 invokeAndWait 會導致 actionPerformed 永不返回,EDT 無法派發事件,死鎖發生。

總結

Swing 線程不安全,非 EDT 線程訪問組件必須透過 SwingUtilities.invokeLater(異步)或 invokeAndWait(同步)委派給 EDT 執行。選擇取決於是否需要等待更新完成並獲取返回值。

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