Swing 單線程規則要求耗時任務(數據庫批量操作、複雜計算、遠程調用)**決不能在 EDT(事件分發線程)執行**,否則界面凍結、無法重繪、用戶以為程序崩潰。本文提供一套完整、可復用的「線程等待提示框」方案:後台線程執行任務、模態對話框阻塞用戶操作、專用監控線程檢測任務完成自動關閉提示框,附完整代碼與關鍵點解析。
JDialog(「正在執行,請等待...」),阻塞用戶交互但允許重繪。join() 等待任務線程結束,然後在 EDT 中關閉提示框。
public class TestFrame extends JFrame {
public TestFrame() {
// 初始化 GUI(Windows 風格、居中、按鈕)
testButton.addActionListener(e -> {
TestThread taskThread = new TestThread(); // 1. 創建任務線程
taskThread.start(); // 2. 啟動任務
new ThreadDiag(this, taskThread, "正在執行,請等待......").start(); // 3. 啟動提示框+監控
});
}
public static void main(String[] args) {
new TestFrame().setVisible(true);
}
}
public class TestThread extends Thread {
@Override public void run() {
for (int i = 1; i < 100000; i++) {
System.out.println(i); // 模擬耗時操作
}
}
}
public class ThreadDiag extends Thread {
private final JFrame parentFrame;
private final Thread taskThread; // 被監控的任務線程
private final String message;
private JDialog dialog;
public ThreadDiag(JFrame parent, Thread taskThread, String message) {
this.parentFrame = parent;
this.taskThread = taskThread;
this.message = message;
}
@Override public void run() {
// 此 run() 由 start() 在新線程調用,但 Swing 組件創建/顯示必須在 EDT!
// 修正:使用 SwingUtilities.invokeLater 確保在 EDT 構建並顯示對話框
SwingUtilities.invokeLater(() -> {
initDialog();
dialog.setVisible(true); // 模態顯示,阻塞此 EDT 事件處理,但允許重繪
});
}
private void initDialog() {
dialog = new JDialog(parentFrame, "正在執行", true); // true = 模態
dialog.setCursor(Cursor.getPredefinedCursor(Cursor.WAIT_CURSOR));
JPanel panel = new JPanel();
panel.add(new JLabel(message));
dialog.getContentPane().add(panel);
dialog.pack();
dialog.setLocationRelativeTo(parentFrame);
// 4. 啟動監控線程(非 EDT),等待任務結束關閉對話框
new DisposeDiag(dialog, taskThread).start();
}
}
class DisposeDiag extends Thread {
private final JDialog dialog;
private final Thread taskThread;
DisposeDiag(JDialog dialog, Thread taskThread) {
this.dialog = dialog; this.taskThread = taskThread;
}
@Override public void run() {
try {
taskThread.join(); // 阻塞等待任務線程終止
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
// 任務結束,在 EDT 安全關閉對話框
SwingUtilities.invokeLater(() -> dialog.dispose());
}
}
JDialog(..., true)setVisible(true) 會阻塞調用線程(此處為 EDT 事件處理線程),直到 dispose() 或 setVisible(false)。| 線程 | 職責 | 關鍵 API |
|---|---|---|
| 任務線程 | 執行耗時業務邏輯 | Thread.start() |
| 提示框線程 | 在 EDT 構建/顯示模態對話框,啟動監控線程 | SwingUtilities.invokeLater, JDialog.setVisible(true) |
| 監控線程 | join() 等待任務線程,EDT 關閉對話框 | Thread.join(), SwingUtilities.invokeLater |
invokeLater/invokeAndWait)。原文中 ThreadDiag.run() 直接在新線程創建/顯示 JDialog,**違反單線程規則**,極易導致界面異常。本方案修正為 invokeLater 確保 EDT 操作。
public final class SwingTaskDialog {
public static T executeWithDialog(Component parent, String message, Callable task)
throws Exception {
JDialog dialog = new JDialog(SwingUtilities.getWindowAncestor(parent), "處理中", true);
JLabel label = new JLabel(message);
dialog.add(label);
dialog.pack();
dialog.setLocationRelativeTo(parent);
FutureTask futureTask = new FutureTask<>(task);
new Thread(futureTask).start(); // 任務線程
new Thread(() -> {
try { futureTask.get(); } // 阻塞等待任務完成/異常
catch (Exception ignored) {}
finally { SwingUtilities.invokeLater(dialog::dispose); }
}).start(); // 監控線程
dialog.setVisible(true); // EDT 顯示模態對話框,阻塞至此
return futureTask.get(); // 重新獲取結果/拋異常
}
}
// 使用示例
try {
String result = SwingTaskDialog.executeWithDialog(frame, "正在導出...", () -> {
exportData(); return "導出完成";
});
JOptionPane.showMessageDialog(frame, result);
} catch (Exception ex) {
ex.printStackTrace();
JOptionPane.showMessageDialog(frame, "失敗: " + ex.getMessage());
}
JDialog 是阻塞用戶操作、保持界面存活的標準方式。join() 任務線程,invokeLater 關閉對話框,職責單一、線程安全。SwingTaskDialog,業務代碼一行調用,消除樣板代碼。掌握此模式,讓你的 Swing 應用在處理複雜後台任務時依然保持專業、流暢的用戶體驗。