F-LAB JAVA · 4주차 · Phase 6 · 스레드 간 협력
이 Unit을 끝내면 다음을 답할 수 있어야 한다.
인터럽트 (Interrupt) 는 한 스레드가 다른 스레드에게 "중단을 요청" 하는 협력적 메커니즘이며, 강제 종료가 아니라 인터럽트 플래그를 설정하는 신호일 뿐이다.
interrupt()는 대상 스레드의 인터럽트 플래그를 true 로 설정하고,isInterrupted()는 플래그를 확인 (변경 X),interrupted()는 static 메서드로 현재 스레드의 플래그를 확인한 후 false 로 리셋한다.
wait()·sleep()·join()등 블로킹 메서드는 인터럽트되면InterruptedException을 던지면서 플래그를 false 로 리셋 하므로, 인터럽트 정보를 유지하려면Thread.currentThread().interrupt()로 플래그를 복원 해야 한다.
인터럽트는 강제 종료가 아니므로 스레드가 플래그를 확인하고 스스로 종료 해야 하며 (협력적 종료), 무한 루프에서는while (!Thread.currentThread().isInterrupted())패턴으로 인터럽트 시 깔끔히 빠져나온다.
인터럽트를 무시하면 (catch 후 아무것도 안 함) 종료 신호가 사라져 스레드가 멈추지 않으므로, 최소한 플래그를 복원하거나 종료해야 한다.
인터럽트 = "그만하라" 어깨 두드리기:
interrupt() = 어깨 두드림 (요청):
- "이제 그만 하는 게 어때?"
- 강제 X (요청일 뿐)
- 플래그 설정 (신호)
스레드의 선택 (협력적):
- 플래그 확인 → 스스로 종료
- 또는 무시 (계속 — 위험)
isInterrupted() = 어깨 봤나 확인:
- 두드림 확인 (플래그 그대로)
interrupted() = 확인 + 잊기 (static):
- 두드림 확인 + 리셋
- 현재 스레드만
InterruptedException = 자다가 두드림:
- sleep/wait 중 두드리면
- 깜짝 깨어남 (예외)
- 두드림 흔적 지움 (플래그 리셋)
- → 다시 두드린 척 (복원) 필요
협력적 종료:
- 두드리면 스스로 정리하고 나감
- 강제로 끌어내지 X (안전)
→ 인터럽트 = 중단 요청 (강제 X), 플래그 신호, 협력적 종료.
1. 인터럽트의 정의
2. 강제 종료가 아닌 이유
3. interrupt / isInterrupted / interrupted
4. interrupted()의 static과 리셋
5. InterruptedException과 플래그
6. 플래그 복원
7. 협력적 종료 패턴
8. 즉각 종료 패턴
9. 면접 + 자기 점검
인터럽트 (Interrupt):
한 스레드가 다른 스레드에게
"중단을 요청" 하는 협력적 메커니즘.
특징:
- 요청일 뿐 (강제 X)
- 플래그 설정
- 스레드가 확인하고 처리
인터럽트 플래그:
각 스레드가 가진 boolean 상태.
- interrupt(): true 설정
- isInterrupted(): 확인
- interrupted(): 확인 + 리셋
→ 인터럽트 요청 표시
Thread worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
doWork(); // 인터럽트 안 됐으면 계속
}
cleanup(); // 인터럽트 시 정리
});
worker.start();
worker.interrupt(); // 중단 요청 (플래그 설정)
인터럽트 용도:
- 스레드 종료 요청
- 작업 취소
- 타임아웃
- graceful shutdown
핵심:
- 협력적 중단
- 강제 X
@Service
public class InterruptBasics {
private Thread worker;
public void startWorker() {
worker = new Thread(() -> {
while (!Thread.currentThread().isInterrupted()) {
processNextShipment(); // 작업
}
log.info("Worker stopped gracefully");
cleanup();
});
worker.start();
}
public void stopWorker() {
if (worker != null) {
worker.interrupt(); // 중단 요청
}
}
private void processNextShipment() { }
private void cleanup() { }
}
인터럽트의 정의는?
답:
1. 정의:
플래그:
특징:
용도:
인터럽트는 협력적:
강제 종료 X.
- 플래그만 설정
- 스레드가 확인하고 스스로 종료
- 무시도 가능 (위험)
이유:
- 안전 (자원 정리)
- 일관성 (중간 상태 방지)
강제 종료 (stop()) 의 위험:
Thread.stop() (deprecated):
- 즉시 강제 종료
- 락 즉시 반납 (중간 상태)
- 자원 정리 X
- 데이터 손상
→ deprecated (사용 금지)
→ 인터럽트 (협력적) 권장
협력적 중단의 장점:
- 안전한 종료 시점 선택
- 자원 정리 (cleanup)
- 일관성 유지
- 데이터 손상 방지
스레드가:
- 안전한 지점에서
- 정리하고
- 종료
// ❌ stop() — 강제 (deprecated)
worker.stop(); // 즉시 종료 (위험)
// 중간 상태, 자원 누수
// ✓ interrupt() — 협력적
worker.interrupt(); // 요청
// 스레드가:
while (!Thread.currentThread().isInterrupted()) {
work();
}
cleanup(); // 안전하게 정리
// 인터럽트 무시 (가능하지만 위험)
Thread worker = new Thread(() -> {
while (true) { // 플래그 확인 X
doWork();
// 인터럽트 무시 → 영원히
}
});
worker.start();
worker.interrupt(); // 효과 없음 (무시됨)
// → 플래그 확인 필요
@Service
public class CooperativeInterrupt {
// ✓ 협력적 종료
public void startCooperative() {
Thread worker = new Thread(() -> {
try {
while (!Thread.currentThread().isInterrupted()) {
Shipment shipment = takeShipment();
process(shipment); // 진행 중 작업 완료
}
} finally {
// 안전한 정리
flushPendingData();
closeResources();
log.info("Worker cleaned up");
}
});
worker.start();
}
private Shipment takeShipment() { return null; }
private void process(Shipment s) { }
private void flushPendingData() { }
private void closeResources() { }
}
인터럽트가 강제 종료가 아닌 이유는?
답:
1. 협력적:
강제 위험:
장점:
무시 가능:
// 1. interrupt() — 인터럽트 요청 (인스턴스)
void interrupt();
// 2. isInterrupted() — 플래그 확인 (인스턴스)
boolean isInterrupted();
// 3. interrupted() — 확인 + 리셋 (static)
static boolean interrupted();
interrupt():
대상 스레드의 인터럽트 플래그를 true.
worker.interrupt();
→ worker 의 플래그 설정
- 인스턴스 메서드
- 대상 지정
isInterrupted():
대상 스레드의 플래그 확인.
- 플래그 변경 X (그대로)
- 인스턴스 메서드
worker.isInterrupted();
→ worker 의 플래그 반환
interrupted():
현재 스레드의 플래그 확인 + 리셋.
- 확인 후 false 로 리셋
- static 메서드 (현재 스레드)
Thread.interrupted();
→ 현재 스레드 플래그 확인 + 리셋
| 메서드 | 대상 | 플래그 변경 | 타입 |
|---|---|---|---|
| interrupt() | 지정 스레드 | true 설정 | 인스턴스 |
| isInterrupted() | 지정 스레드 | 변경 X | 인스턴스 |
| interrupted() | 현재 스레드 | 확인 후 리셋 | static |
public class InterruptMethods {
public void demonstrate() {
Thread worker = new Thread(() -> {
// isInterrupted — 확인 (리셋 X)
while (!Thread.currentThread().isInterrupted()) {
doWork();
}
// 플래그 그대로 (다음 확인도 true)
});
worker.start();
// interrupt — 요청
worker.interrupt(); // worker 플래그 설정
// isInterrupted — 외부 확인
boolean interrupted = worker.isInterrupted(); // 확인 (그대로)
}
public void interruptedExample() {
// interrupted — 현재 스레드 확인 + 리셋
if (Thread.interrupted()) { // 확인 + 리셋
// 인터럽트됨 (이제 플래그 false)
handleInterrupt();
}
}
private void doWork() { }
private void handleInterrupt() { }
}
interrupt / isInterrupted / interrupted 차이는?
답:
1. interrupt():
isInterrupted():
interrupted():
핵심:
interrupted() 가 static 인 이유:
현재 스레드의 플래그를 다룸.
- Thread.currentThread() 대상
- static (현재 스레드 묵시적)
Thread.interrupted();
≡ 현재 스레드의 플래그
리셋하는 이유:
인터럽트 처리 후 플래그 초기화.
- 한 번 처리하면 리셋
- 같은 인터럽트 중복 처리 방지
확인 + 리셋 = "인터럽트 소비"
// interrupted() 동작
Thread.currentThread().interrupt(); // 플래그 true
boolean first = Thread.interrupted(); // true (확인 + 리셋)
boolean second = Thread.interrupted(); // false (이미 리셋)
// 첫 호출: true (그리고 리셋)
// 둘째 호출: false (리셋됨)
// isInterrupted — 리셋 X
Thread.currentThread().interrupt();
boolean a = Thread.currentThread().isInterrupted(); // true
boolean b = Thread.currentThread().isInterrupted(); // true (그대로)
// interrupted — 리셋 O
Thread.currentThread().interrupt();
boolean c = Thread.interrupted(); // true (리셋)
boolean d = Thread.interrupted(); // false (리셋됨)
// ❌ 실수 — interrupted 로 플래그 소비
public void wrong() {
if (Thread.interrupted()) { // 확인 + 리셋
// 처리
}
// 플래그 리셋됨
// 이후 코드에서 인터럽트 모름
}
// ✓ 확인만 하려면 isInterrupted
public void correct() {
if (Thread.currentThread().isInterrupted()) { // 확인만
// 처리
}
// 플래그 그대로
}
public class InterruptedStaticExample {
// interrupted — 인터럽트 소비 (처리)
public void processWithInterruptCheck() {
while (true) {
if (Thread.interrupted()) { // 확인 + 리셋
log.info("Interrupt consumed, stopping");
break; // 처리 후 종료
}
doWork();
}
}
// isInterrupted — 확인만 (소비 X)
public void checkOnly() {
while (!Thread.currentThread().isInterrupted()) { // 확인만
doWork();
}
// 플래그 유지 (cleanup 에서 다시 확인 가능)
}
private void doWork() { }
}
interrupted()가 static이고 플래그를 리셋하는 이유는?
답:
1. static:
리셋:
동작:
주의:
블로킹 메서드 + 인터럽트:
wait(), sleep(), join() 등:
- 인터럽트되면 InterruptedException
- 플래그 false 로 리셋!
→ 예외 + 플래그 리셋
try {
Thread.sleep(1000); // 인터럽트되면
} catch (InterruptedException e) {
// 여기 도달
// ★ 플래그는 이미 false (리셋됨)
boolean flag = Thread.currentThread().isInterrupted(); // false!
}
예외 + 리셋 이유:
- 예외로 인터럽트 알림
- 플래그는 리셋 (예외가 처리)
- 중복 방지
문제:
- 예외 잡고 플래그 모름
- 상위에서 인터럽트 못 알아챔
- → 복원 필요 (다음 섹션)
InterruptedException 던지는 메서드:
- Thread.sleep()
- Object.wait()
- Thread.join()
- BlockingQueue.put/take
- Lock.lockInterruptibly()
- Condition.await()
- Future.get()
모두 인터럽트 시 예외 + 플래그 리셋
InterruptedException 흐름:
스레드: sleep(1000) (TIMED_WAITING)
플래그 false
interrupt() → 플래그 true → sleep 깨어남
→ InterruptedException 던짐
→ 플래그 false (리셋!)
catch (InterruptedException e):
플래그는 false (리셋됨)
public class InterruptedExceptionExample {
public void demonstrateReset() {
Thread worker = new Thread(() -> {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
// 인터럽트로 sleep 깨어남
// ★ 플래그는 이미 false
log.info("Interrupted, flag: {}",
Thread.currentThread().isInterrupted()); // false!
// 플래그 복원 필요 (다음 섹션)
Thread.currentThread().interrupt(); // 복원
}
});
worker.start();
worker.interrupt();
}
}
InterruptedException 발생 시 플래그 상태는?
답:
1. 리셋:
이유:
던지는 메서드:
문제:
플래그 복원:
InterruptedException catch 후
플래그가 리셋됨.
복원:
Thread.currentThread().interrupt();
이유:
- 상위 코드가 인터럽트 알도록
- 인터럽트 정보 유지
// ✓ 플래그 복원
public void method() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 복원
// 상위가 인터럽트 알 수 있음
}
}
// ❌ 복원 안 함 (인터럽트 정보 손실)
public void wrong() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// 아무것도 안 함 (플래그 리셋된 채)
}
// 상위 코드:
while (!Thread.currentThread().isInterrupted()) {
// 플래그 false → 인터럽트 무시
// 영원히 루프
}
}
인터럽트 처리 방법:
1. 복원 (재설정)
- Thread.currentThread().interrupt()
- 상위가 처리
2. 전파 (예외 던지기)
- throws InterruptedException
- 호출자가 처리
3. 처리 (종료)
- 직접 종료/정리
→ 절대 무시 X
// 방법 1: 복원
public void restore() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 복원
}
}
// 방법 2: 전파 (가능하면)
public void propagate() throws InterruptedException {
Thread.sleep(1000); // 던짐 (호출자 처리)
}
// 방법 3: 종료
public void terminate() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return; // 종료
}
}
@Service
public class FlagRestoreExample {
// ✓ 복원
public void processWithRestore() {
while (!Thread.currentThread().isInterrupted()) {
try {
Shipment shipment = queue.take(); // 인터럽트 가능
process(shipment);
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 복원
break; // 루프 종료
}
}
cleanup();
}
// ✓ 전파 (라이브러리 메서드)
public Shipment takeWithPropagate() throws InterruptedException {
return queue.take(); // 던짐 (호출자 처리)
}
private final BlockingQueue<Shipment> queue = new LinkedBlockingQueue<>();
private void process(Shipment s) { }
private void cleanup() { }
}
인터럽트 플래그 복원의 필요성은?
답:
1. 복원:
이유:
안 하면:
방법:
협력적 종료:
스레드가 인터럽트를 확인하고
스스로 안전하게 종료.
요소:
- 플래그 확인
- InterruptedException 처리
- 자원 정리
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 작업
doWork(); // 인터럽트 가능
}
} catch (InterruptedException e) {
// 블로킹 중 인터럽트
Thread.currentThread().interrupt();
} finally {
// 안전한 정리
cleanup();
}
}
인터럽트 확인 두 지점:
1. 플래그 확인 (반복 작업)
- while (!isInterrupted())
- CPU 작업 사이
2. InterruptedException (블로킹)
- sleep, wait, take
- 블로킹 중 인터럽트
→ 둘 다 처리
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// CPU 작업 (플래그 확인)
heavyComputation();
// 블로킹 작업 (예외)
Shipment s = queue.take(); // InterruptedException
process(s);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 복원
} finally {
cleanup();
}
}
// graceful shutdown
public class GracefulWorker {
private Thread worker;
public void start() {
worker = new Thread(this::run);
worker.start();
}
public void shutdown() throws InterruptedException {
worker.interrupt(); // 중단 요청
worker.join(5000); // 최대 5초 대기
if (worker.isAlive()) {
log.warn("Worker did not stop in time");
}
}
private void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
processTask();
}
} finally {
cleanup(); // 정리
}
}
private void processTask() { }
private void cleanup() { }
}
@Service
public class CooperativeShutdown {
private Thread processingWorker;
private final BlockingQueue<Shipment> queue = new LinkedBlockingQueue<>();
@PostConstruct
public void start() {
processingWorker = new Thread(this::processLoop, "shipment-worker");
processingWorker.start();
}
private void processLoop() {
try {
while (!Thread.currentThread().isInterrupted()) {
Shipment shipment = queue.take(); // 블로킹 (인터럽트 가능)
process(shipment);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt(); // 복원
log.info("Worker interrupted, shutting down");
} finally {
flushPending(); // 정리
}
}
@PreDestroy
public void shutdown() throws InterruptedException {
processingWorker.interrupt(); // 중단 요청
processingWorker.join(10000); // 대기
}
private void process(Shipment s) { }
private void flushPending() { }
}
협력적 종료 패턴은?
답:
1. 협력적:
두 지점:
패턴:
graceful:
// 즉각 종료 패턴
while (!Thread.interrupted()) { // 확인 + 리셋
doWork();
}
// 인터럽트 시 즉시 종료
// 방법 1: isInterrupted (플래그 유지)
while (!Thread.currentThread().isInterrupted()) {
doWork();
}
// 종료 후 플래그 true (cleanup 에서 확인 가능)
// 방법 2: interrupted (플래그 리셋)
while (!Thread.interrupted()) {
doWork();
}
// 종료 후 플래그 false (소비됨)
isInterrupted vs interrupted 루프:
isInterrupted (권장):
- 플래그 유지
- 이후 코드도 인터럽트 알 수 있음
interrupted:
- 플래그 리셋
- 인터럽트 소비
- 이후 코드 모름
→ 보통 isInterrupted (유지)
// 빠른 종료 — 여러 지점 확인
public void run() {
while (!Thread.currentThread().isInterrupted()) {
// 긴 작업 중간에도 확인
for (int i = 0; i < 1000; i++) {
if (Thread.currentThread().isInterrupted()) {
return; // 빠른 종료
}
processItem(i);
}
}
}
// 블로킹 작업 + 즉각 종료
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
Shipment s = queue.take(); // 인터럽트 시 즉시 예외
process(s);
}
} catch (InterruptedException e) {
// 즉시 종료 (예외로 빠짐)
Thread.currentThread().interrupt();
}
}
// take 가 인터럽트 시 즉시 예외 → 빠른 종료
@Service
public class ImmediateShutdown {
private final BlockingQueue<Shipment> queue = new LinkedBlockingQueue<>();
// 즉각 종료 패턴
public void run() {
try {
while (!Thread.currentThread().isInterrupted()) {
// 블로킹 — 인터럽트 시 즉시 예외
Shipment shipment = queue.take();
// 긴 처리 중에도 확인
processWithChecks(shipment);
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
log.info("Immediate shutdown");
}
}
private void processWithChecks(Shipment shipment) throws InterruptedException {
// 단계별 인터럽트 확인 (빠른 종료)
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
validateShipment(shipment);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedException();
}
calculateFreight(shipment);
}
private void validateShipment(Shipment s) { }
private void calculateFreight(Shipment s) { }
}
즉각 종료 패턴은?
답:
1. 패턴:
선택:
빠른 종료:
즉시:
| Q | 핵심 답변 |
|---|---|
| 인터럽트? | 중단 요청 (협력적) |
| 강제 종료? | 아니다 (플래그) |
| interrupt()? | 플래그 설정 |
| isInterrupted()? | 확인 (변경 X) |
| interrupted()? | 확인 + 리셋 (static) |
| InterruptedException 플래그? | 리셋됨 |
| 복원? | interrupt() 재호출 |
| 협력적 종료? | 플래그 확인 + 정리 |
| 즉각 종료? | while (!interrupted()) |
| 인터럽트 무시? | 위험 (안 멈춤) |
답:
답:
답:
답:
답:
1. 인터럽트
2. 세 메서드
3. 예외와 복원
이번 Unit에서 인터럽트를 봤다면, 다음은 yield() (Phase 6 마지막).
🚀 Phase 6 — 스레드 간 협력
✅ Unit 6.1 생산자-소비자 문제
✅ Unit 6.2 wait()과 notify()
✅ Unit 6.3 인터럽트 메커니즘 ← 여기
⏭ Unit 6.4 yield() — Phase 6 완주
✅ Phase 1~5 (21 Unit, 1차 정점 완료)
🚀 Phase 6 — 스레드 협력 (3/4 진행)
총: 24/35 Unit