1. EDT (Event Dispatch Thread)란?
Java의 그래픽 애플리케이션 — Swing이든 JavaFX든 — 에서는 모든 사용자 작업(클릭, 키 입력)과 창의 리페인트가 EDT(Event Dispatch Thread, 이벤트 디스패치 스레드)라는 전용 스레드에서 처리됩니다.
왜 필요한가? Java의 UI 컴포넌트는 스레드 세이프하지 않습니다. 레이스 컨디션과 화면 깨짐을 피하려면, 모든 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는 작업을 큐에 넣어 순서대로 처리하며, 한 번에 하나만 실행할 수 있습니다. 무거운 작업으로 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 스레드 세이프티를 위반하기 때문입니다. 컴포넌트 변경은 반드시 인터페이스 스레드에서만 수행해야 합니다.
백그라운드 작업을 위한 전용 클래스
실제 애플리케이션에서는 진행률 표시, 취소, 오류 처리가 자주 필요합니다. 이를 위해 다음이 제공됩니다:
- 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와 오래 걸리는 작업에서 흔한 실수
오류 1: EDT에서 오래 걸리는 작업 실행. 애플리케이션 전체가 ‘멈추고’, 창이 반응하지 않으며, 사용자는 프로그램이 고장 났다고 생각합니다.
오류 2: 백그라운드 스레드에서 UI를 업데이트하려 함. UI 스레드 세이프티를 위반하면 버그, 화면 깨짐, 크래시로 이어질 수 있습니다. SwingUtilities.invokeLater 또는 Platform.runLater를 사용하세요.
오류 3: 백그라운드 작업에서 오류 처리를 하지 않음. 예외가 ‘사라져서’ 사용자는 무엇이 잘못됐는지 알 수 없습니다. Swing에서는 done()을 오버라이드하고 get()을 읽으세요. JavaFX에서는 setOnFailed에 구독하세요.
오류 4: 오래 걸리는 작업을 취소할 수 없음. 사용자는 로드/계산을 중단할 수 없습니다. 취소 지원(SwingWorker.cancel, Task.cancel)을 사용하고 작업 내부에서 취소 플래그를 확인하세요.
오류 5: 진행률 표시가 없음. 사용자는 프로그램이 ‘멈췄다’고 느낍니다. Swing에서는 SwingWorker의 결과 게시와 ProgressBar를 함께 사용하고, JavaFX에서는 updateProgress와 시각적 인디케이터를 사용하세요.
GO TO FULL VERSION