1. EDT (Event Dispatch Thread) とは何か
Java のグラフィカルアプリケーションでは — Swing であれ JavaFX であれ — ユーザー操作(クリック、キー入力)やウィンドウの再描画は、EDT(Event Dispatch Thread、イベント配送スレッド)という専用スレッドで処理されます。
なぜ必要なのか? Java の UI コンポーネントはスレッドセーフではありません。レースや表示乱れを避けるため、インターフェースの変更は厳密に一つの場所 — EDT — で行われます。レジに店員が一人だけいるようなもので、同じ伝票を複数人が同時に扱うことはできません。
Swing では EDT がイベントハンドラやコンポーネントの再描画(例: actionPerformed)を実行します。JavaFX では相当するのが JavaFX Application Thread で、そこで UI 更新や setOnAction のようなハンドラが実行されます。
2. UI における「長時間処理」の問題
EDT で長時間処理を走らせると何が起きる?
ユーザーがボタンを押すと、ハンドラ(たとえば actionPerformed や setOnAction)は EDT 上で実行されます。その中で重いタスク(大きなファイルの読み込み、ネットワークリクエスト、複雑な計算)を動かすと、UI 全体が「フリーズ」します:
- ウィンドウがクリックやキー入力に反応しなくなる。
- 再描画が止まり、移動しても画面が固まったままになる。
- ユーザーはプログラムが「壊れた」と思ってしまう。
悪い例(Swing):
button.addActionListener(e -> {
// EDT 上で長時間処理を実行している!
longOperation(); // 例: 大きなファイルの読み込み
label.setText("完了!");
});
結果: longOperation() の実行中、ウィンドウはユーザーに反応しません。
なぜ? EDT はタスクを順番に処理し、一度に一つしか実行できません。あなたの長時間処理で忙しい間は、クリックや再描画を処理できないのです。
3. 解決策: 長時間処理は必ずバックグラウンドスレッドで
原則:
- 長時間処理はすべてバックグラウンドスレッドで行う。
- UI の変更は EDT/JavaFX Application Thread のみで行う。
長時間処理を別スレッドで起動する
例(Swing):
button.addActionListener(e -> {
new Thread(() -> {
longOperation(); // バックグラウンドスレッドで実行される
// 次に UI を更新する必要があるが、EDT からのみ!
SwingUtilities.invokeLater(() -> label.setText("完了!"));
}).start();
});
例(JavaFX):
button.setOnAction(e -> {
new Thread(() -> {
longOperation();
// Platform.runLater で UI を更新する
Platform.runLater(() -> label.setText("完了!"));
}).start();
});
バックグラウンドスレッドから UI をどう更新するか?
- Swing: SwingUtilities.invokeLater(Runnable) を使う — タスクは EDT のキューに入る。
- JavaFX: Platform.runLater(Runnable) を使う — タスクは JavaFX Application Thread で実行される。
なぜ単に label.setText(...) をバックグラウンドから呼んではいけないのか? UI のスレッドセーフ性に反するからです。コンポーネントの変更は UI スレッドからのみ行う必要があります。
バックグラウンドタスク用の専用クラス
実際のアプリでは、進捗表示・キャンセル・エラー処理が必要になることがよくあります。これには次のものが使えます:
- SwingWorker<T, V> — Swing 向け;
- Task<V>、Service<V> — JavaFX 向け。
例(JavaFX Task):
Task<Void> task = new Task<>() {
@Override
protected Void call() throws Exception {
longOperation();
// 進捗を更新できる: updateProgress(...)
return null;
}
};
task.setOnSucceeded(e -> label.setText("完了!"));
task.setOnFailed(e -> label.setText("エラー!"));
new Thread(task).start();
利点: 進捗、キャンセル、成功/失敗イベント。UI の変更は、安全なメソッド(updateMessage、updateProgress)やハンドラ(setOnSucceeded など)を通じて行う。
4. 良い/悪いパターン
悪い例: イベントハンドラ内で長時間処理
button.setOnAction(e -> longOperation()); // UI がフリーズする!
良い例: 長時間処理は別スレッドで
button.setOnAction(e -> new Thread(() -> longOperation()).start());
さらに良い: Task/Worker を使う
JavaFX:
button.setOnAction(e -> {
Task<Void> task = new Task<>() {
@Override
protected Void call() throws Exception {
longOperation();
return null;
}
};
task.setOnSucceeded(ev -> label.setText("完了!"));
new Thread(task).start();
});
Swing:
button.addActionListener(e -> {
SwingWorker<Void, Void> worker = new SwingWorker<>() {
@Override
protected Void doInBackground() throws Exception {
longOperation();
return null;
}
@Override
protected void done() {
label.setText("完了!");
}
};
worker.execute();
});
5. 実践: ファイル読み込みの例
JavaFX:
button.setOnAction(e -> {
Task<String> task = new Task<>() {
@Override
protected String call() throws Exception {
// 時間のかかる読み込みの模擬
Thread.sleep(2000);
return "ファイルが読み込まれました!";
}
};
task.setOnSucceeded(ev -> label.setText(task.getValue()));
new Thread(task).start();
});
Swing:
button.addActionListener(e -> {
SwingWorker<String, Void> worker = new SwingWorker<>() {
@Override
protected String doInBackground() throws Exception {
Thread.sleep(2000);
return "ファイルが読み込まれました!";
}
@Override
protected void done() {
try {
label.setText(get());
} catch (Exception ex) {
label.setText("エラー!");
}
}
};
worker.execute();
});
6. EDT と長時間処理で起きがちなミス
エラー No.1: EDT で長時間処理を実行する。 アプリ全体が「フリーズ」し、ウィンドウが反応せず、ユーザーはプログラムが壊れたと思ってしまう。
エラー No.2: バックグラウンドスレッドから UI を更新しようとする。 UI のスレッドセーフ性に反し、バグや表示乱れ、クラッシュの原因になる。SwingUtilities.invokeLater または Platform.runLater を使う。
エラー No.3: バックグラウンドタスクでのエラー処理がない。 例外が「消えて」しまい、ユーザーは何が起きたか分からない。Swing では done() をオーバーライドして get() を読む。JavaFX では setOnFailed を購読する。
エラー No.4: 長時間処理をキャンセルできない。 ユーザーが読み込みや計算を中断できない。キャンセルをサポートする(SwingWorker.cancel、Task.cancel)とともに、タスク内でキャンセルフラグを確認する。
エラー No.5: 進捗表示がない。 ユーザーはアプリが「フリーズ」したと思ってしまう。Swing では結果の公開とプログレスバーを SwingWorker と併用し、JavaFX では updateProgress と可視化されたインジケータを使う。
GO TO FULL VERSION