Swing 的線程安全架構建立在一個核心規則之上:一旦 Swing 組件被具現化,所有可能影響或依賴組件狀態的代碼都必須在事件派發線程(Event Dispatch Thread, EDT)中執行。 這項規則雖然聽起來嚴格,但對於大多數簡單應用程式,開發者無需過度擔心線程問題。
paint() 方法已被調用或可能被調用。頂層窗口(如 JFrame)在調用 setVisible(true)、show() 或 pack() 時會被具現化;將組件加入已具現化的容器也會觸發具現化。paint() 和 actionPerformed() 會自動在 EDT 中運行。開發者可透過 SwingUtilities.invokeLater() 將代碼提交給 EDT 執行。並非所有 Swing 方法都受單線程規則限制,以下屬於線程安全可從任何線程調用:
This method is thread safe, although most Swing methods are not. 的方法。JComponent 的 repaint()、revalidate()、invalidate() 方法。addListenerTypeListener()、removeListenerTypeListener()。pack() 前),並在具現化後不再執行 GUI 代碼。init() 方法中構建 GUI(前提是不調用 show() 或 setVisible(true))。當需要從非事件處理代碼(如背景線程、網絡回調)更新 UI 時,必須使用 SwingUtilities 提供的兩個方法:
Runnable doWorkRunnable = new Runnable() {
public void run() {
doWork(); // 更新 UI 的代碼
}
};
SwingUtilities.invokeLater(doWorkRunnable);
該方法立即返回,不等待代碼執行完成,適合大多數 UI 更新場景。
void showHelloThereDialog() throws Exception {
Runnable showModalDialog = new Runnable() {
public void run() {
JOptionPane.showMessageDialog(myMainFrame, "Hello There");
}
};
SwingUtilities.invokeAndWait(showModalDialog);
}
會阻塞調用線程直到 EDT 執行完畢。除非必要(如需獲取 UI 狀態返回值),優先使用 invokeLater()。使用時需確保調用線程不持有其他線程需要的鎖,避免死鎖。
長時間運算、I/O 操作若在 EDT 執行會導致界面凍結,必須放入背景線程。Swing 提供兩大工具類:
SwingWorker(需單獨下載,不包含在標準 JDK 中)封裝了背景線程創建與結果獲取的複雜細節:
final SwingWorker worker = new SwingWorker() {
public Object construct() {
return new ExpensiveDialogComponent(); // 耗時操作
}
};
worker.start(); // 啟動背景線程
// 事件處理中獲取結果(會阻塞直到完成)
JOptionPane.showMessageDialog(f, worker.get());
流程:start() 啟動線程 → 執行 construct() → get() 等待並返回結果。
javax.swing.Timer 適合動畫、定時輪詢等場景,其 actionPerformed() 自動在 EDT 執行,無需手動 invokeLater():
public class Animator extends JFrame implements ActionListener {
private Timer timer;
private int frameNumber = 0;
public Animator(int delay) {
timer = new Timer(delay, this);
timer.setInitialDelay(0);
timer.setCoalesce(true);
}
public void startAnimation() { timer.start(); }
public void stopAnimation() { timer.stop(); }
public void actionPerformed(ActionEvent e) {
frameNumber++;
repaint(); // 安全:已在 EDT
}
}
invokeLater 任務)從同一隊列按順序處理,避免競態條件。「線程的使用可能極具欺騙性……在幾乎所有情況下,它們都使得調試、測試和維護的困難大大增加甚至成為不可能。」—— SubArctic Java Toolkit 作者