F-LAB JAVA · 4주차 · Phase 7 · Executor 프레임워크
🏆 Phase 7 완주 — ★ 4주차 2차 정점 완료
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
스레드 풀은 사용 후 반드시 종료해야 하며,
shutdown()은 진행·대기 작업을 모두 완료한 뒤 종료하고,shutdownNow()는 진행 작업을 인터럽트하고 대기 작업을 반환하며 즉시 종료를 시도한다.
shutdown()은 새 작업 수락을 거부하되 이미 제출된 작업 (실행 중 + 큐 대기) 은 모두 완료하는 우아한 종료다.
shutdownNow()는 실행 중인 작업에 인터럽트를 보내고 큐에 대기 중이던 미실행 작업 목록 (List<Runnable>) 을 반환하며 즉시 종료를 시도한다 (인터럽트를 처리하지 않는 작업은 계속 실행될 수 있음).
awaitTermination(timeout)은 종료가 완료될 때까지 (또는 타임아웃까지) 블로킹하여 대기한다.
권장 패턴은 shutdown() → awaitTermination() → (타임아웃 시) shutdownNow() 순서의 graceful shutdown 으로, 종료하지 않으면 스레드가 살아남아 JVM 이 종료되지 않거나 자원이 누수된다.
스레드 풀 종료 = 가게 마감:
shutdown() = 정상 마감:
- "더 이상 주문 안 받습니다"
- 단, 받은 주문은 다 완료
- 손님 다 나가면 마감
shutdownNow() = 비상 마감:
- "지금 당장 닫습니다"
- 진행 중 요리 중단 (인터럽트)
- 대기 주문 목록 반환 (미처리)
awaitTermination() = 마감 확인:
- "다 끝났나?" 기다림
- 시간 제한 (10분만 대기)
graceful shutdown = 우아한 마감:
1. 주문 그만 받기 (shutdown)
2. 완료 기다리기 (awaitTermination)
3. 너무 오래 걸리면 강제 (shutdownNow)
종료 안 하면:
- 직원이 안 퇴근 (스레드 살아있음)
- 가게 못 닫음 (JVM 종료 X)
→ shutdown (정상), shutdownNow (비상), awaitTermination (대기), graceful 패턴.
1. shutdown()의 동작
2. shutdownNow()의 동작
3. shutdown vs shutdownNow
4. awaitTermination()
5. graceful shutdown 패턴
6. 종료 후 작업 제출
7. 종료 상태 조회
8. Phase 7 완주 정리
9. 면접 + 자기 점검 + Phase 7 졸업 시험
shutdown():
우아한 종료.
- 새 작업 수락 거부
- 이미 제출된 작업 완료
(실행 중 + 큐 대기)
- 완료 후 종료
shutdown 흐름:
shutdown() 호출
↓
새 작업 거부 (RejectedExecutionException)
↓
실행 중 작업 완료
↓
큐 대기 작업 모두 처리
↓
스레드 종료
↓
Terminated
ExecutorService executor = Executors.newFixedThreadPool(4);
// 작업 제출
executor.submit(task1);
executor.submit(task2);
// 종료 시작
executor.shutdown();
// task1, task2 완료까지 실행
// 새 작업은 거부
// executor.submit(task3); // RejectedExecutionException
shutdown 은 블로킹 X:
shutdown() 호출:
- 즉시 반환 (블로킹 X)
- 종료 "시작"만
- 실제 종료는 백그라운드
종료 대기하려면:
- awaitTermination()
@Service
public class ShutdownExample {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
public void submitWork(Shipment shipment) {
executor.submit(() -> process(shipment));
}
@PreDestroy
public void shutdown() {
executor.shutdown(); // 우아한 종료 시작
// 제출된 작업 모두 완료 후 종료
// 새 작업 거부
log.info("스레드 풀 종료 시작");
}
private void process(Shipment s) { }
}
shutdown()의 동작은?
답:
1. 정의:
흐름:
블로킹 X:
대기:
shutdownNow():
즉시 종료 시도.
- 실행 중 작업 인터럽트
- 큐 대기 작업 반환 (List<Runnable>)
- 즉시 종료 시도
shutdownNow 흐름:
shutdownNow() 호출
↓
새 작업 거부
↓
실행 중 작업 인터럽트 (interrupt())
↓
큐 대기 작업 반환 (미실행)
↓
즉시 종료 시도
ExecutorService executor = Executors.newFixedThreadPool(2);
// 많은 작업 제출
for (int i = 0; i < 100; i++) {
executor.submit(task);
}
// 즉시 종료
List<Runnable> notExecuted = executor.shutdownNow();
// notExecuted: 큐에 대기 중이던 미실행 작업들
log.info("미실행 작업: {}개", notExecuted.size());
인터럽트 처리 의존:
shutdownNow 는 인터럽트만 보냄:
- 작업이 인터럽트 처리해야 멈춤
- 처리 안 하면 계속 실행
→ 협력적 (강제 X)
→ 작업이 인터럽트 확인 필요
// ❌ 인터럽트 무시 작업
executor.submit(() -> {
while (true) { // 인터럽트 확인 X
doWork();
}
// shutdownNow 해도 안 멈춤
});
// ✓ 인터럽트 처리
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
doWork();
}
// shutdownNow 시 멈춤
});
@Service
public class ShutdownNowExample {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
public void emergencyShutdown() {
// 즉시 종료
List<Runnable> pending = executor.shutdownNow();
log.warn("긴급 종료: 미실행 {}개", pending.size());
// 미실행 작업 저장 (나중에 재처리)
for (Runnable task : pending) {
if (task instanceof ShipmentTask st) {
deferredRepository.save(st.getShipment());
}
}
}
// 작업은 인터럽트 처리 필요
public void submitInterruptible(Shipment shipment) {
executor.submit(() -> {
while (!Thread.currentThread().isInterrupted()) {
process(shipment);
break;
}
// shutdownNow 시 인터럽트로 멈춤
});
}
private void process(Shipment s) { }
record ShipmentTask(Shipment shipment) implements Runnable {
public void run() {}
Shipment getShipment() { return shipment; }
}
}
shutdownNow()의 동작은?
답:
1. 정의:
반환:
인터럽트 의존:
협력적:
| 항목 | shutdown() | shutdownNow() |
|---|---|---|
| 새 작업 | 거부 | 거부 |
| 실행 중 | 완료 | 인터럽트 |
| 큐 대기 | 완료 | 반환 (미실행) |
| 반환값 | void | List |
| 속도 | 느림 (완료) | 빠름 (즉시) |
| 손실 | X | 미실행 손실 |
핵심 차이:
shutdown:
- 모두 완료 (우아)
- 손실 X
shutdownNow:
- 즉시 (인터럽트)
- 미실행 손실 (반환)
선택:
shutdown:
- 정상 종료
- 작업 완료 보장
- 일반적
shutdownNow:
- 긴급 종료
- 즉시 멈춤
- 작업 포기 OK
shutdown vs shutdownNow:
shutdown:
실행 중: [████████] 완료
큐 대기: [████] 처리
→ 모두 완료 후 종료
shutdownNow:
실행 중: [████|인터럽트] 중단
큐 대기: [반환] 미실행
→ 즉시 종료
// graceful: shutdown 먼저, 안 되면 shutdownNow
executor.shutdown();
try {
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
// 30초 내 안 끝나면
executor.shutdownNow(); // 강제
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
@Service
public class ShutdownComparison {
private final ExecutorService normalExecutor = Executors.newFixedThreadPool(4);
private final ExecutorService urgentExecutor = Executors.newFixedThreadPool(4);
// 정상 종료 — shutdown
public void normalShutdown() {
normalExecutor.shutdown();
// 모든 배송 처리 완료 후 종료
}
// 긴급 종료 — shutdownNow
public void urgentShutdown() {
List<Runnable> pending = urgentExecutor.shutdownNow();
// 즉시 중단, 미처리 반환
savePendingTasks(pending);
}
private void savePendingTasks(List<Runnable> tasks) { }
}
shutdown vs shutdownNow 차이는?
답:
1. shutdown:
shutdownNow:
선택:
함께:
boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException;
awaitTermination 역할:
종료 완료까지 대기 (블로킹).
- shutdown 후 호출
- 모든 작업 완료까지
- 타임아웃 가능
반환:
- true: 종료 완료
- false: 타임아웃 (미완료)
executor.shutdown(); // 종료 시작 (블로킹 X)
// 종료 대기
boolean terminated = executor.awaitTermination(30, TimeUnit.SECONDS);
if (terminated) {
log.info("정상 종료");
} else {
log.warn("타임아웃 (미완료)");
executor.shutdownNow(); // 강제
}
shutdown + awaitTermination:
shutdown():
- 종료 시작 (블로킹 X)
awaitTermination():
- 완료 대기 (블로킹 O)
→ shutdown 후 awaitTermination
→ 완료 보장
// 긴 종료 대기 (반복)
executor.shutdown();
while (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
log.info("아직 종료 중...");
// 계속 대기 또는 shutdownNow
}
log.info("종료 완료");
@Service
public class AwaitTerminationExample {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
@PreDestroy
public void shutdown() {
executor.shutdown(); // 종료 시작
try {
// 최대 30초 대기
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
log.warn("30초 내 미종료, 강제 종료");
executor.shutdownNow(); // 강제
// 추가 대기
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
log.error("종료 실패");
}
}
log.info("스레드 풀 정상 종료");
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
awaitTermination()의 역할은?
답:
1. 역할:
반환:
사용:
타임아웃 시:
// graceful shutdown 표준 패턴
public void gracefulShutdown(ExecutorService executor) {
executor.shutdown(); // 1. 새 작업 거부
try {
// 2. 완료 대기
if (!executor.awaitTermination(30, TimeUnit.SECONDS)) {
// 3. 타임아웃 시 강제
executor.shutdownNow();
// 4. 강제 후 대기
if (!executor.awaitTermination(10, TimeUnit.SECONDS)) {
log.error("종료 실패");
}
}
} catch (InterruptedException e) {
// 5. 인터럽트 시 강제
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
graceful shutdown 단계:
1. shutdown()
- 새 작업 거부
2. awaitTermination(timeout)
- 완료 대기
3. (타임아웃) shutdownNow()
- 강제 종료
4. awaitTermination(추가)
- 강제 후 대기
5. (인터럽트) shutdownNow()
- 예외 처리
순서 이유:
먼저 우아하게 (shutdown):
- 작업 완료 기회
안 되면 강제 (shutdownNow):
- 무한 대기 방지
→ 우아 시도 후 강제
→ 균형
// Spring — @PreDestroy
@Service
public class GracefulService {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
@PreDestroy
public void destroy() {
shutdownGracefully(executor, 30);
}
private void shutdownGracefully(ExecutorService executor, int seconds) {
executor.shutdown();
try {
if (!executor.awaitTermination(seconds, TimeUnit.SECONDS)) {
executor.shutdownNow();
}
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
}
// 또는 Spring ThreadPoolTaskExecutor
// setWaitForTasksToCompleteOnShutdown(true)
// setAwaitTerminationSeconds(30)
// JVM 종료 훅
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
log.info("JVM 종료, 스레드 풀 정리");
executor.shutdown();
try {
executor.awaitTermination(10, TimeUnit.SECONDS);
} catch (InterruptedException e) {
executor.shutdownNow();
}
}));
@Service
public class ShipmentGracefulShutdown {
private final ExecutorService executor = Executors.newFixedThreadPool(8);
@PreDestroy
public void shutdown() {
log.info("배송 처리 풀 종료 시작");
executor.shutdown(); // 1. 새 작업 거부
try {
// 2. 진행 배송 완료 대기 (60초)
if (!executor.awaitTermination(60, TimeUnit.SECONDS)) {
log.warn("60초 초과, 강제 종료");
// 3. 강제 종료 + 미처리 회수
List<Runnable> pending = executor.shutdownNow();
log.warn("미처리 배송: {}개", pending.size());
savePendingShipments(pending);
// 4. 강제 후 대기
if (!executor.awaitTermination(15, TimeUnit.SECONDS)) {
log.error("배송 풀 종료 실패");
}
}
log.info("배송 처리 풀 정상 종료");
} catch (InterruptedException e) {
executor.shutdownNow();
Thread.currentThread().interrupt();
}
}
private void savePendingShipments(List<Runnable> tasks) { }
}
graceful shutdown 패턴은?
답:
1. 패턴:
단계:
이유:
Spring:
종료 후 제출:
shutdown/shutdownNow 후:
- 새 작업 제출 시
- RejectedExecutionException
→ 종료 시작하면 거부
executor.shutdown();
// 종료 후 제출
try {
executor.submit(task); // RejectedExecutionException
} catch (RejectedExecutionException e) {
log.warn("종료된 풀에 제출");
}
종료 후 거부:
RejectedExecutionHandler 적용:
- AbortPolicy: 예외 (기본)
- 커스텀: 다르게 처리
→ 종료 = 거부 상황
// 제출 전 상태 확인
if (!executor.isShutdown()) {
executor.submit(task);
} else {
log.warn("풀 종료됨, 제출 불가");
// 대체 처리
}
@Service
public class SubmitAfterShutdown {
private final ExecutorService executor = Executors.newFixedThreadPool(4);
private volatile boolean shuttingDown = false;
public boolean submitShipment(Shipment shipment) {
if (shuttingDown || executor.isShutdown()) {
log.warn("종료 중, 제출 거부: {}", shipment.getId());
deferredQueue.offer(shipment); // 대체 처리
return false;
}
try {
executor.submit(() -> process(shipment));
return true;
} catch (RejectedExecutionException e) {
log.warn("제출 거부 (종료): {}", shipment.getId());
deferredQueue.offer(shipment);
return false;
}
}
@PreDestroy
public void shutdown() {
shuttingDown = true;
executor.shutdown();
}
private final Queue<Shipment> deferredQueue = new ConcurrentLinkedQueue<>();
private void process(Shipment s) { }
}
종료 후 작업 제출 시 어떻게 되나?
답:
1. 거부:
거부 정책:
확인:
대체:
boolean isShutdown(); // 종료 시작 여부
boolean isTerminated(); // 종료 완료 여부
isShutdown:
shutdown/shutdownNow 호출 후 true.
- 종료 "시작" 여부
- 작업 완료 여부 무관
isTerminated:
모든 작업 완료 + 종료 후 true.
- 완전 종료 여부
- shutdown 후 작업 끝나면
상태 흐름:
Running:
- isShutdown: false
- isTerminated: false
shutdown() 후 (작업 진행):
- isShutdown: true
- isTerminated: false
작업 완료 후:
- isShutdown: true
- isTerminated: true
executor.shutdown();
// 종료 시작 확인
if (executor.isShutdown()) {
log.info("종료 시작됨");
}
// 완전 종료 확인 (폴링 대신 awaitTermination 권장)
while (!executor.isTerminated()) {
Thread.sleep(100);
}
log.info("완전 종료");
// 더 나은 방법
executor.awaitTermination(30, TimeUnit.SECONDS);
@Service
public class TerminationStatus {
private final ThreadPoolExecutor executor = new ThreadPoolExecutor(
4, 8, 60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(100)
);
// 상태 모니터링
public void logStatus() {
log.info("Shutdown: {}, Terminated: {}, Active: {}, Queue: {}",
executor.isShutdown(),
executor.isTerminated(),
executor.getActiveCount(),
executor.getQueue().size());
}
// 헬스 체크
public boolean isHealthy() {
return !executor.isShutdown(); // 종료 안 됨 = 정상
}
@PreDestroy
public void shutdown() throws InterruptedException {
executor.shutdown();
if (executor.awaitTermination(30, TimeUnit.SECONDS)) {
log.info("정상 종료 (isTerminated: {})", executor.isTerminated());
}
}
}
종료 상태 조회는?
답:
1. isShutdown:
isTerminated:
흐름:
활용:
Phase 7 — Executor 프레임워크 (★ 2차 정점)
Unit 7.1 — 스레드 풀의 필요성
Unit 7.2 — Executor와 ExecutorService
Unit 7.3 — Future와 Callable
Unit 7.4 — ThreadPoolExecutor 내부 (★ 마스터)
Unit 7.5 — 스레드 풀 종류
Unit 7.6 — 작업 큐와 거부 정책
Unit 7.7 — 스레드 풀 종료
Executor 프레임워크:
1. 필요성
- 직접 생성 문제
2. 추상화
- Executor/ExecutorService
3. 결과
- Future/Callable
4. 내부
- ThreadPoolExecutor
5. 종류
- Fixed/Cached/Single 등
6. 큐/정책
- 작업 관리
7. 종료
- graceful shutdown
Phase 7 → Phase 8:
- 스레드 풀 → 고급 비동기
Phase 8 — 고급 비동기 (3 Unit):
- CompletableFuture (★ 마스터)
- ForkJoinPool
- RecursiveTask
Phase 7 핵심 통찰 5가지:
1. 스레드 풀
- 재사용, 자원 보호
2. Executor 추상화
- 제출과 실행 분리
3. ThreadPoolExecutor
- core → 큐 → max → 거부
4. 풀 종류
- 용도별 선택
5. graceful shutdown
- shutdown → await → shutdownNow
✅ Phase 1 — 동시성의 기초 (4 Unit)
✅ Phase 2 — 4분면 매트릭스 (3 Unit)
✅ Phase 3 — 스레드 다루기 (5 Unit)
✅ Phase 4 — synchronized & volatile (5 Unit) ★ 1차 정점
✅ Phase 5 — Lock 도구 (4 Unit)
✅ Phase 6 — 스레드 협력 (4 Unit)
✅ Phase 7 — Executor (7 Unit) ★ 2차 정점 ← 완주
⏭ Phase 8 — 고급 비동기 (3 Unit)
총: 32/35 Unit (Phase 7 완주, 약 91%)
Phase 7의 종합은?
답:
1. 7개 Unit:
큰 그림:
핵심:
| Q | 핵심 답변 |
|---|---|
| shutdown()? | 우아한 종료 (완료) |
| shutdownNow()? | 즉시 (인터럽트, 반환) |
| 차이? | 완료 vs 인터럽트 |
| awaitTermination()? | 종료 대기 |
| graceful 패턴? | shutdown→await→shutdownNow |
| 종료 후 제출? | 거부 |
| isShutdown? | 종료 시작 |
| isTerminated? | 종료 완료 |
| 종료 안 하면? | 스레드 누수, JVM 종료 X |
| Phase 7 핵심? | Executor 프레임워크 |
Q1. 직접 생성 문제? → 비용, 무제한, 관리
Q2. 생성 비용? → OS 스레드, 1MB 스택
Q3. 무제한 위험? → OOM
Q4. 스레드 풀? → 재사용
Q5. 재사용 효과? → 비용 절감
Q6. 구성 요소? → 워커, 큐, 팩토리, 정책
Q7. 처리량 제어? → 동시 수 제한
Q8. 백프레셔? → 과부하 방지
Q9. 적정 크기 CPU? → 코어 + 1
Q10. 적정 크기 I/O? → 더 많이
Q11. 생산자-소비자? → 작업 큐 + 워커
Q12. 장점? → 비용/자원/관리
Q13. Executor? → execute
Q14. ExecutorService? → submit, shutdown
Q15. execute vs submit? → void vs Future
Q16. 예외 처리? → 핸들러 vs get
Q17. Executors? → 팩토리 (위험)
Q18. invokeAll? → 모두 대기
Q19. invokeAny? → 하나만
Q20. Callable? → 반환값 + 예외
Q21. Runnable vs Callable? → void vs V
Q22. Future? → 결과 핸들
Q23. get()? → 블로킹
Q24. cancel()? → 취소
Q25. Future 한계? → 블로킹, 조합
Q26. 파라미터? → core/max/keepAlive/queue/handler
Q27. core vs max? → 평상시 vs 최대
Q28. keepAlive? → 유휴 정리
Q29. 작업 흐름? → core→큐→max→거부
Q30. 큐 vs max? → 큐 먼저
Q31. 무제한 큐? → max 무의미
Q32. 거부 정책? → max + 큐 가득
Q33. AbortPolicy? → 예외
Q34. CallerRunsPolicy? → 호출자 (백프레셔)
Q35. DiscardPolicy? → 버림
Q36. LinkedBlockingQueue? → 무제한/제한
Q37. ArrayBlockingQueue? → 고정
Q38. SynchronousQueue? → 직접 전달
Q39. Fixed? → 고정, 무제한 큐
Q40. Cached? → 동적, 무제한 스레드
Q41. Single? → 순차
Q42. Scheduled? → 예약
Q43. WorkStealing? → 일 훔치기
Q44. Virtual? → Java 21+ I/O
Q45. shutdown? → 우아한 종료
Q46. shutdownNow? → 즉시, 반환
Q47. awaitTermination? → 종료 대기
Q48. graceful? → shutdown→await→shutdownNow
Q49. 종료 후 제출? → 거부
Q50. isTerminated? → 완전 종료
50 / 50 → Phase 7 마스터
45-49 → 거의 마스터
40-44 → 복습
< 40 → Unit 7.1 ~ 7.7 재학습
답:
답:
답:
답:
답:
1. shutdown vs shutdownNow
2. awaitTermination
3. graceful shutdown
🚀 Phase 7 — Executor 프레임워크 (★ 2차 정점)
✅ Unit 7.1 스레드 풀의 필요성
✅ Unit 7.2 Executor와 ExecutorService
✅ Unit 7.3 Future와 Callable
✅ Unit 7.4 ThreadPoolExecutor 내부 (★ 마스터)
✅ Unit 7.5 스레드 풀 종류
✅ Unit 7.6 작업 큐와 거부 정책
✅ Unit 7.7 스레드 풀 종료 ← 여기, Phase 7 완주
→ 스레드 풀 + Executor + Future
→ ThreadPoolExecutor 내부
→ ★ 4주차 2차 정점 완료
Phase 8 — 고급 비동기 (3 Unit, 4주차 마지막)
Unit 8.1 — CompletableFuture (★ 마스터)
Unit 8.2 — ForkJoinPool
Unit 8.3 — RecursiveTask
✅ Phase 1~7 (32 Unit, 1·2차 정점 완료)
⏭ Phase 8 — 고급 비동기 (3 Unit)
총: 32/35 Unit (약 91%)
🏆 Phase 7 완주 — Executor 프레임워크 마스터 (★ 2차 정점 완료)