4주차 Unit 6.2 — wait()과 notify()

Psj·2026년 5월 21일

F-lab

목록 보기
142/230

Unit 6.2 — wait()과 notify()

F-LAB JAVA · 4주차 · Phase 6 · 스레드 간 협력


📌 학습 목표

이 Unit을 끝내면 다음을 답할 수 있어야 한다.

  • wait() 의 동작 (락 반납 + 대기) 은?
  • wait() 가 synchronized 안에서만 가능한 이유는?
  • notify() / notifyAll() 의 차이는?
  • while 이 아닌 if 를 쓰면 안 되는 이유는?
  • spurious wakeup (가짜 깨어남) 이란?
  • wait vs sleep 의 차이 (락 반납) 는?
  • notify vs notifyAll 선택 기준은?
  • 생산자-소비자를 wait/notify 로 구현 하는 법은?
  • wait/notify 의 한계 는?

🎯 핵심 한 문장

wait() 은 synchronized 블록 안에서 호출되어, 현재 스레드가 보유한 모니터 락을 반납하고 WAITING 상태로 대기하다가, 다른 스레드의 notify() / notifyAll() 로 깨어나 락을 재획득한 뒤 진행한다.
wait/notify 는 모니터 락이 필요 하므로 반드시 synchronized 블록 안에서 호출해야 한다 (아니면 IllegalMonitorStateException).
notify() 는 대기 중인 스레드 하나를 깨우고, notifyAll() 은 모두 깨우며, 일반적으로 notifyAll 이 안전하다 (깨울 스레드를 잘못 선택할 위험 없음).
조건 확인은 반드시 if 가 아닌 while 로 해야 한다 — wait 에서 깨어났어도 조건이 여전히 만족되지 않을 수 있고 (다른 스레드가 먼저 처리), spurious wakeup (가짜 깨어남) 으로 notify 없이 깨어날 수도 있기 때문이다.
wait 는 락을 반납 하지만 sleep락을 유지 한다는 점이 핵심 차이다.

비유 — 대기실의 호출 시스템

wait/notify = 병원 대기실:

wait() = 진료실 앞에서 대기:
  - 차례 아니면 대기 (WAITING)
  - 자리 양보 (락 반납)
  - 호출될 때까지

notify() = 한 명 호출:
  - "다음 분 들어오세요" (1명)

notifyAll() = 모두 호출:
  - "모두 다시 확인하세요" (전부)
  - 각자 자기 차례인지 재확인

while 재확인 (if 안 되는 이유):
  - 호출받아 일어났는데
  - 이미 다른 사람이 들어감 (조건 안 됨)
  - 다시 앉아서 대기 (while 로 재확인)
  - if 면 그냥 진행 (잘못! 차례 아닌데)

spurious wakeup:
  - 호출 안 했는데 일어남 (가짜)
  - while 로 재확인하면 안전

wait vs sleep:
  - wait: 자리 양보 (락 반납)
  - sleep: 자리 지킴 (락 유지)

→ wait = 락 반납 + 대기, notify = 깨움, while 로 조건 재확인 필수.


🧭 9개 섹션 로드맵

1. wait()의 동작
2. synchronized 안에서만
3. notify()와 notifyAll()
4. while vs if (조건 재확인)
5. spurious wakeup
6. wait vs sleep
7. notify vs notifyAll 선택
8. 생산자-소비자 구현
9. 면접 + 자기 점검

1️⃣ wait()의 동작

1.1 wait() 의 정의

wait():

  현재 스레드가 보유한 모니터 락을 반납하고
  WAITING 상태로 대기.

동작:
  1. 락 반납
  2. WAITING 상태
  3. notify/notifyAll 까지 대기
  4. 깨어나면 락 재획득

1.2 wait() 흐름

wait() 흐름:

스레드:
  synchronized 진입 (락 보유)
    ↓ wait()
  락 반납 + WAITING
    ↓ (다른 스레드 notify)
  깨어남 (대기)
    ↓ 락 재획득
  RUNNABLE (synchronized 안 재개)

1.3 코드

synchronized (lock) {
    while (!condition) {
        lock.wait();   // 락 반납 + 대기
        // notify 시 깨어남 + 락 재획득
    }
    // 조건 충족 후 진행
}

1.4 wait() 의 종류

// 무한 대기
void wait() throws InterruptedException;

// 타임아웃
void wait(long timeout) throws InterruptedException;
void wait(long timeout, int nanos) throws InterruptedException;

// 모두 InterruptedException
// Object 의 메서드

1.5 락 재획득

wait 후 락 재획득:

  notify 로 깨어나도:
    - 즉시 진행 X
    - 락 재획득 필요
    - 다른 스레드가 락 보유 중일 수 있음

  락 획득 후:
    - synchronized 안 재개

1.6 ILIC 의 맥락

@Service
public class WaitExample {
    
    private final Object lock = new Object();
    private boolean dataReady = false;
    private Shipment data;
    
    // 대기 (소비자)
    public Shipment waitForData() throws InterruptedException {
        synchronized (lock) {
            while (!dataReady) {
                lock.wait();   // 락 반납 + 대기
            }
            dataReady = false;
            return data;   // 데이터 받음
        }
    }
    
    // 통지 (생산자)
    public void provideData(Shipment shipment) {
        synchronized (lock) {
            data = shipment;
            dataReady = true;
            lock.notify();   // 대기 스레드 깨움
        }
    }
}

1.7 자기 점검 답변

wait()의 동작은?

:
1. 정의:

  • 락 반납 + WAITING
  1. 흐름:

    • 락 반납 → 대기 → notify → 락 재획득
  2. 종류:

    • wait() (무한)
    • wait(ms) (타임아웃)
  3. 재획득:

    • 깨어나도 락 필요

2️⃣ synchronized 안에서만

2.1 모니터 락 필요

wait/notify 는 synchronized 안에서만:

  이유:
    - wait/notify 는 모니터 락 사용
    - 락 보유 상태에서만 호출
    - 아니면 IllegalMonitorStateException

2.2 IllegalMonitorStateException

// ❌ synchronized 밖에서 wait
public void wrong() throws InterruptedException {
    lock.wait();   // IllegalMonitorStateException
    // 락 없이 wait 호출
}

// ✓ synchronized 안에서
public void correct() throws InterruptedException {
    synchronized (lock) {
        lock.wait();   // OK (락 보유)
    }
}

2.3 왜 락이 필요한가

왜 락이 필요한가:

  wait/notify 는 조건 동기화.
  - 조건 확인 (공유 상태)
  - 대기/통지
  - 원자적이어야

  락 없으면:
    - 조건 확인과 wait 사이 경쟁
    - lost notification

→ 락으로 보호

2.4 같은 락 객체

// wait/notify 는 같은 락 객체에
synchronized (lock) {
    lock.wait();   // lock 의 모니터
}

synchronized (lock) {
    lock.notify();   // 같은 lock 의 모니터
}

// 다른 객체면:
synchronized (lockA) {
    lockB.wait();   // ❌ lockB 락 없음 → 예외
}

2.5 lost notification

lost notification (락 없으면):

  // 락 없는 가상 시나리오
  if (!condition) {       // 확인
    // ← 여기서 다른 스레드 notify
    wait();               // 놓침 (이미 notify 됨)
  }

  → 영원히 대기 (신호 놓침)

락으로:
  - 확인 + wait 원자적
  - notify 도 락 안
  - 안전

2.6 ILIC 의 맥락

@Service
public class SynchronizedRequirement {
    
    private final Object lock = new Object();
    private boolean ready = false;
    
    // ✓ synchronized 안에서 wait
    public void consumer() throws InterruptedException {
        synchronized (lock) {   // 락 필요
            while (!ready) {
                lock.wait();   // 같은 lock
            }
            process();
        }
    }
    
    // ✓ synchronized 안에서 notify
    public void producer() {
        synchronized (lock) {   // 같은 lock
            ready = true;
            lock.notify();
        }
    }
    
    // ❌ 잘못된 예
    public void wrong() throws InterruptedException {
        // lock.wait();   // synchronized 밖 → 예외
    }
    
    private void process() { }
}

2.7 자기 점검 답변

wait()가 synchronized 안에서만 가능한 이유는?

:
1. 모니터 락:

  • wait/notify 는 락 사용
  • 락 보유 상태에서만
  1. 예외:

    • synchronized 밖 → IllegalMonitorStateException
  2. 이유:

    • 조건 확인 + wait 원자적
    • lost notification 방지
  3. 같은 객체:

    • wait/notify 같은 락

3️⃣ notify()와 notifyAll()

3.1 notify()

notify():

  대기 중인 스레드 하나를 깨움.

특징:
  - 임의의 하나 (선택 불가)
  - 나머지는 계속 대기
  - synchronized 안에서

3.2 notifyAll()

notifyAll():

  대기 중인 모든 스레드를 깨움.

특징:
  - 모두 깨움
  - 각자 조건 재확인
  - 하나만 진행 (락 경쟁)

3.3 비교

synchronized (lock) {
    ready = true;
    
    lock.notify();      // 하나만 깨움
    // 또는
    lock.notifyAll();   // 모두 깨움
}

3.4 notify 의 위험

notify() 의 위험:

  깨울 스레드를 선택 X (임의).

문제:
  - 잘못된 스레드 깨움
  - 조건 안 맞는 스레드
  - 다시 wait
  - 진짜 처리할 스레드는 대기

  → lost wakeup 가능

3.5 notifyAll 이 안전

notifyAll() 안전:

  모두 깨움 → 각자 확인:
    - 조건 맞는 스레드 진행
    - 아닌 스레드 다시 wait

  단점:
    - 모두 깨어남 (오버헤드)
    - 락 경쟁

  하지만 안전 (lost wakeup X)

3.6 ILIC 의 맥락

@Service
public class NotifyExample {
    
    private final Object lock = new Object();
    private final Queue<Shipment> queue = new LinkedList<>();
    
    public Shipment consume() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) {
                lock.wait();   // 대기
            }
            return queue.poll();
        }
    }
    
    public void produce(Shipment shipment) {
        synchronized (lock) {
            queue.offer(shipment);
            lock.notifyAll();   // 모두 깨움 (안전)
            // notify() 면 잘못된 스레드 깨울 수도
        }
    }
    
    // notifyAll: 여러 소비자가 모두 깨어나
    // 큐에서 하나씩 가져감 (나머지 다시 wait)
}

3.7 자기 점검 답변

notify()와 notifyAll()의 차이는?

:
1. notify():

  • 하나 깸 (임의)
  • 나머지 대기
  1. notifyAll():

    • 모두 깸
    • 각자 재확인
  2. notify 위험:

    • 잘못된 스레드
    • lost wakeup
  3. notifyAll 안전:

    • 모두 확인
    • 오버헤드 (하지만 안전)

4️⃣ while vs if (조건 재확인)

4.1 while 필수

조건 확인은 while:

  ✓ while (!condition) { wait(); }
  ❌ if (!condition) { wait(); }

이유:
  - 깨어나도 조건 재확인
  - spurious wakeup 대비
  - 다른 스레드가 먼저 처리 가능

4.2 if 의 문제

// ❌ if (위험)
synchronized (lock) {
    if (queue.isEmpty()) {
        lock.wait();
    }
    return queue.poll();   // ★ 위험
    // 깨어났을 때 큐가 다시 비었을 수 있음
    // → null 반환 또는 예외
}

// ✓ while (안전)
synchronized (lock) {
    while (queue.isEmpty()) {
        lock.wait();
    }
    return queue.poll();   // 조건 재확인됨 (안전)
}

4.3 왜 깨어나도 조건 안 맞나

깨어나도 조건 안 맞는 경우:

1. 다른 스레드가 먼저 처리:
   - notifyAll 로 여러 깨어남
   - 하나가 큐 비움
   - 나머지는 빈 큐

2. spurious wakeup:
   - notify 없이 깨어남

→ 깨어나면 조건 재확인 (while)

4.4 시각화

while vs if:

notifyAll 로 소비자 A, B 깨어남:

if (위험):
  A: 깨어남 → poll (데이터 1개 가져감)
  B: 깨어남 → poll (큐 비었는데!) → null/예외

while (안전):
  A: 깨어남 → while 재확인 → 데이터 있음 → poll
  B: 깨어남 → while 재확인 → 큐 비었음 → 다시 wait

4.5 표준 패턴

// wait 표준 패턴 (항상 while)
synchronized (lock) {
    while (!조건) {   // while 필수
        lock.wait();
    }
    // 조건 충족 (재확인됨)
    실제_작업();
}

4.6 ILIC 의 맥락

@Service
public class WhileVsIfExample {
    
    private final Object lock = new Object();
    private final Queue<Shipment> queue = new LinkedList<>();
    
    // ✓ while (안전)
    public Shipment consumeSafe() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) {   // while
                lock.wait();
                // 깨어나도 다시 확인
            }
            return queue.poll();   // 안전 (조건 보장)
        }
    }
    
    // ❌ if (위험)
    public Shipment consumeUnsafe() throws InterruptedException {
        synchronized (lock) {
            if (queue.isEmpty()) {   // if (위험)
                lock.wait();
            }
            return queue.poll();   // 큐 비었을 수도 → null
        }
    }
    
    public void produce(Shipment shipment) {
        synchronized (lock) {
            queue.offer(shipment);
            lock.notifyAll();
        }
    }
}

4.7 자기 점검 답변

while이 아닌 if를 쓰면 안 되는 이유는?

:
1. while 필수:

  • 깨어나도 재확인
  1. if 위험:

    • 조건 안 맞아도 진행
    • null/예외
  2. 이유:

    • 다른 스레드 먼저 처리
    • spurious wakeup
  3. 표준:

    • while (!조건) wait()

5️⃣ spurious wakeup

5.1 spurious wakeup

spurious wakeup (가짜 깨어남):

  notify 없이 wait 가 깨어나는 현상.

원인:
  - OS/JVM 구현
  - 하드웨어
  - 드물지만 발생 가능

대비:
  - while 로 조건 재확인

5.2 왜 발생하나

spurious wakeup 원인:

  - OS 의 조건 변수 구현
  - 시그널 처리
  - 성능 최적화

  → 명세상 가능
  → 방어적 코딩 필요

5.3 대비 — while

// spurious wakeup 대비
synchronized (lock) {
    while (!condition) {   // while
        lock.wait();
        // spurious wakeup 으로 깨어나도
        // while 로 조건 재확인
        // 조건 안 맞으면 다시 wait
    }
}

// if 면:
// spurious wakeup 시 조건 안 맞는데 진행 (버그)

5.4 LockSupport 도 동일

spurious wakeup 은 보편적:

  - Object.wait()
  - Condition.await()
  - LockSupport.park()

  모두 spurious wakeup 가능
  → 모두 while 로 재확인

5.5 실무 권장

실무 권장:

  조건 대기는 항상:
    while (!조건) {
        wait();   // 또는 await, park
    }

  - 절대 if 안 씀
  - spurious wakeup + 다중 깨어남 대비
  - 방어적 코딩

5.6 ILIC 의 맥락

@Service
public class SpuriousWakeupHandling {
    
    private final Object lock = new Object();
    private boolean taskAvailable = false;
    
    public void waitForTask() throws InterruptedException {
        synchronized (lock) {
            while (!taskAvailable) {   // while (spurious 대비)
                lock.wait();
                // spurious wakeup 또는 notify
                // 둘 다 while 로 재확인
            }
            taskAvailable = false;
            processTask();
        }
    }
    
    public void provideTask() {
        synchronized (lock) {
            taskAvailable = true;
            lock.notifyAll();
        }
    }
    
    private void processTask() { }
    
    // 핵심: while 로 spurious wakeup + 다중 깨어남 모두 대비
}

5.7 자기 점검 답변

spurious wakeup이란?

:
1. 정의:

  • notify 없이 깨어남
  • 가짜 깨어남
  1. 원인:

    • OS/JVM 구현
    • 명세상 가능
  2. 대비:

    • while 로 재확인
  3. 보편:

    • wait, await, park 모두

6️⃣ wait vs sleep

6.1 핵심 차이 — 락

wait vs sleep 핵심:

wait():
  - 락 반납 O
  - 다른 스레드 락 획득 가능

sleep():
  - 락 반납 X (유지)
  - 다른 스레드 락 못 얻음

6.2 비교 표

항목wait()sleep()
클래스ObjectThread
락 반납OX
호출 위치synchronized 안어디서나
깨우기notify/시간시간/인터럽트
상태WAITINGTIMED_WAITING
용도조건 대기시간 지연

6.3 락 반납 비교

// wait — 락 반납
synchronized (lock) {
    lock.wait();   // 락 반납
    // 다른 스레드가 lock 획득 가능
}

// sleep — 락 유지
synchronized (lock) {
    Thread.sleep(1000);   // 락 유지!
    // 다른 스레드 lock 못 얻음 (1초 동안)
}

6.4 sleep 의 락 유지 문제

// ❌ synchronized 안 sleep (락 점유)
public void badSleep() throws InterruptedException {
    synchronized (lock) {
        Thread.sleep(5000);   // 5초 락 점유!
        // 다른 모든 스레드 BLOCKED
    }
}

// 조건 대기는 wait 사용
public void goodWait() throws InterruptedException {
    synchronized (lock) {
        while (!condition) {
            lock.wait();   // 락 반납 (다른 스레드 진행)
        }
    }
}

6.5 용도 차이

용도:

wait:
  - 조건 대기
  - 다른 스레드와 협력
  - notify 로 깨움

sleep:
  - 단순 시간 지연
  - 폴링 간격
  - 협력 X

6.6 ILIC 의 맥락

@Service
public class WaitVsSleepExample {
    
    private final Object lock = new Object();
    private boolean ready = false;
    
    // wait — 조건 대기 (락 반납)
    public void waitForCondition() throws InterruptedException {
        synchronized (lock) {
            while (!ready) {
                lock.wait();   // 락 반납 → 생산자 진행 가능
            }
            process();
        }
    }
    
    // sleep — 시간 지연 (락 무관)
    public void retryWithDelay() throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            if (tryProcess()) return;
            Thread.sleep(1000);   // 1초 지연 (락 X)
        }
    }
    
    // ❌ 잘못 — synchronized 안 sleep
    public void wrong() throws InterruptedException {
        synchronized (lock) {
            Thread.sleep(5000);   // 락 5초 점유 (나쁨)
        }
    }
    
    private void process() { }
    private boolean tryProcess() { return false; }
}

6.7 자기 점검 답변

wait vs sleep의 차이는?

:
1. 핵심:

  • wait: 락 반납
  • sleep: 락 유지
  1. 클래스:

    • wait: Object
    • sleep: Thread
  2. 위치:

    • wait: synchronized 안
    • sleep: 어디서나
  3. 용도:

    • wait: 조건 대기
    • sleep: 시간 지연

7️⃣ notify vs notifyAll 선택

7.1 선택 기준

notify vs notifyAll:

notify:
  - 하나만 깨움
  - 효율적 (오버헤드 ↓)
  - 위험 (잘못된 스레드)

notifyAll:
  - 모두 깸
  - 안전
  - 오버헤드 (모두 깨어남)

7.2 notify 가 안전한 경우

notify 안전한 경우:

  - 모든 대기 스레드가 동일 조건
  - 깨운 스레드가 반드시 진행
  - 단일 조건

예:
  - 모든 소비자가 같은 큐 대기
  - 아무나 처리 가능
  → notify OK

7.3 notifyAll 이 필요한 경우

notifyAll 필요한 경우:

  - 여러 조건 (다른 대기 이유)
  - 깨운 스레드가 못 진행할 수 있음
  - 안전 우선

예:
  - 생산자/소비자 모두 같은 락 대기
  - notify 면 잘못된 쪽 깨울 수도
  → notifyAll

7.4 lost wakeup 예시

// ❌ notify 위험 (생산자/소비자 같은 락)
synchronized (lock) {
    // 소비자: 큐 비면 wait
    // 생산자: 큐 가득 차면 wait
    // 같은 lock
    
    lock.notify();   // 잘못된 쪽 깨울 수 있음
    // 소비자가 소비자 깨움 (둘 다 대기 지속)
}

// ✓ notifyAll (안전)
synchronized (lock) {
    lock.notifyAll();   // 모두 깨움 → 맞는 쪽 진행
}

7.5 권장

권장:

  기본: notifyAll
    - 안전
    - 버그 적음

  최적화: notify
    - 조건 확실할 때만
    - 성능 중요할 때
    - 신중히

  또는:
    - Condition (여러 조건)
    - BlockingQueue

7.6 ILIC 의 맥락

@Service
public class NotifySelectionExample {
    
    private final Object lock = new Object();
    private final Queue<Shipment> queue = new LinkedList<>();
    private final int capacity = 100;
    
    // 생산자/소비자 같은 락 → notifyAll
    public void produce(Shipment shipment) throws InterruptedException {
        synchronized (lock) {
            while (queue.size() >= capacity) {
                lock.wait();   // 가득 차면 대기
            }
            queue.offer(shipment);
            lock.notifyAll();   // 소비자 깨움 (안전)
        }
    }
    
    public Shipment consume() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) {
                lock.wait();   // 비면 대기
            }
            Shipment s = queue.poll();
            lock.notifyAll();   // 생산자 깨움 (안전)
            return s;
        }
    }
    // 생산자/소비자 모두 같은 lock 대기
    // → notifyAll 필수 (notify 면 같은 쪽 깨울 위험)
}

7.7 자기 점검 답변

notify vs notifyAll 선택 기준은?

:
1. notify:

  • 하나, 효율
  • 위험 (잘못된 스레드)
  1. notifyAll:

    • 모두, 안전
    • 오버헤드
  2. notify 안전:

    • 동일 조건
    • 아무나 진행
  3. 권장:

    • 기본 notifyAll
    • 또는 Condition

8️⃣ 생산자-소비자 구현

8.1 wait/notify 구현

public class ProducerConsumer<T> {
    private final Queue<T> queue = new LinkedList<>();
    private final int capacity;
    private final Object lock = new Object();
    
    public ProducerConsumer(int capacity) {
        this.capacity = capacity;
    }
    
    // 생산자
    public void produce(T item) throws InterruptedException {
        synchronized (lock) {
            while (queue.size() >= capacity) {
                lock.wait();   // 가득 차면 대기
            }
            queue.offer(item);
            lock.notifyAll();   // 소비자 깨움
        }
    }
    
    // 소비자
    public T consume() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) {
                lock.wait();   // 비면 대기
            }
            T item = queue.poll();
            lock.notifyAll();   // 생산자 깨움
            return item;
        }
    }
}

8.2 사용

ProducerConsumer<Task> pc = new ProducerConsumer<>(10);

// 생산자 스레드
new Thread(() -> {
    while (true) {
        Task task = createTask();
        pc.produce(task);
    }
}).start();

// 소비자 스레드
new Thread(() -> {
    while (true) {
        Task task = pc.consume();
        process(task);
    }
}).start();

8.3 핵심 요소

구현의 핵심:

1. synchronized (lock)
   - 임계 영역 보호

2. while (!조건) wait()
   - 조건 대기 (재확인)

3. notifyAll()
   - 상대 깨움

4. 락 반납 (wait)
   - 다른 스레드 진행

8.4 BlockingQueue 와 비교

// wait/notify 직접 (복잡)
// 위 ProducerConsumer

// BlockingQueue (간단, 권장)
BlockingQueue<Task> queue = new LinkedBlockingQueue<>(10);
queue.put(task);     // 생산 (가득 차면 대기)
Task t = queue.take();   // 소비 (비면 대기)

// 내부적으로 wait/notify (또는 Condition)
// 직접 구현 불필요

8.5 Condition 으로 개선

// Condition — 정교한 통지 (Phase 5)
public class ProducerConsumerCondition<T> {
    private final Queue<T> queue = new LinkedList<>();
    private final int capacity;
    private final ReentrantLock lock = new ReentrantLock();
    private final Condition notFull = lock.newCondition();
    private final Condition notEmpty = lock.newCondition();
    
    public void produce(T item) throws InterruptedException {
        lock.lock();
        try {
            while (queue.size() >= capacity) {
                notFull.await();   // 가득 차면
            }
            queue.offer(item);
            notEmpty.signal();   // 소비자만 (정확)
        } finally {
            lock.unlock();
        }
    }
    
    public T consume() throws InterruptedException {
        lock.lock();
        try {
            while (queue.isEmpty()) {
                notEmpty.await();   // 비면
            }
            T item = queue.poll();
            notFull.signal();   // 생산자만 (정확)
            return item;
        } finally {
            lock.unlock();
        }
    }
}
// signal: 정확한 통지 (notifyAll 오버헤드 ↓)

8.6 ILIC 의 맥락

@Service
public class ShipmentProducerConsumer {
    
    private final Queue<Shipment> queue = new LinkedList<>();
    private final int capacity = 100;
    private final Object lock = new Object();
    
    // 생산자 (주문 접수)
    public void submit(Shipment shipment) throws InterruptedException {
        synchronized (lock) {
            while (queue.size() >= capacity) {
                lock.wait();   // 가득 차면 대기 (백프레셔)
            }
            queue.offer(shipment);
            lock.notifyAll();
        }
    }
    
    // 소비자 (워커)
    public Shipment take() throws InterruptedException {
        synchronized (lock) {
            while (queue.isEmpty()) {
                lock.wait();   // 비면 대기
            }
            Shipment shipment = queue.poll();
            lock.notifyAll();
            return shipment;
        }
    }
    
    // 실무: BlockingQueue 권장 (검증됨)
}

8.7 자기 점검 답변

생산자-소비자를 wait/notify로 구현하는 법은?

:
1. 구조:

  • synchronized (lock)
  • while (!조건) wait()
  • notifyAll()
  1. 생산자:

    • 가득 차면 wait
    • 추가 후 notifyAll
  2. 소비자:

    • 비면 wait
    • 꺼낸 후 notifyAll
  3. 개선:

    • Condition (정확)
    • BlockingQueue (권장)

9️⃣ 면접 + 자기 점검

9.1 면접 단골 질문 매핑

Q핵심 답변
wait()?락 반납 + 대기
synchronized 안?모니터 락 필요
notify()?하나 깸
notifyAll()?모두 깸
while vs if?조건 재확인 (while)
spurious wakeup?notify 없이 깸
wait vs sleep?락 반납 vs 유지
notify 위험?잘못된 스레드
생산자-소비자?while + wait + notifyAll
Condition?정확한 통지

9.2 자기 점검 체크리스트

wait

  • 동작 (락 반납)
  • 흐름
  • 종류

synchronized

  • 락 필요
  • 예외

notify/notifyAll

  • 차이
  • 안전성

while vs if

  • 재확인
  • if 위험

spurious wakeup

  • 정의
  • 대비 (while)

wait vs sleep

  • 락 반납
  • 용도

구현

  • 생산자-소비자
  • Condition

9.3 추가 심화 질문

Q1: wait() 의 InterruptedException?

답:

  • wait 중 인터럽트 시 던짐
  • 처리: 재시도 또는 종료
  • 플래그 복원
  • 정상 종료 신호

Q2: notify 후 즉시 깨어나나?

답:

  • 아니다
  • notify 한 스레드가 락 보유 중
  • synchronized 블록 끝나야 락 반납
  • 그 후 대기 스레드 락 경쟁

Q3: wait(timeout) 의 동작?

답:

  • 시간 또는 notify 까지
  • 시간 초과 시 자동 깨어남
  • TIMED_WAITING
  • 타임아웃 후 조건 재확인

Q4: 왜 wait/notify 가 Object 메서드?

답:

  • 모든 객체가 모니터
  • 어떤 객체든 락 + 대기 집합
  • synchronized 와 일관
  • Object 레벨 동기화

Q5: wait/notify vs Condition?

답:

  • wait/notify: synchronized, 조건 1개
  • Condition: ReentrantLock, 여러 조건
  • Condition 이 정교 (signal)
  • 둘 다 직접보다 BlockingQueue 권장

🎯 핵심 요약 — 3줄 정리

1. wait/notify

  • wait: 락 반납 + WAITING (synchronized 안)
  • notify: 하나, notifyAll: 모두 (안전)

2. while과 spurious wakeup

  • while 로 조건 재확인 (if 안 됨)
  • spurious wakeup + 다중 깨어남 대비

3. wait vs sleep

  • wait: 락 반납 (Object, 조건 대기)
  • sleep: 락 유지 (Thread, 시간 지연)

📚 다음으로...

Unit 6.3 — 인터럽트(Interrupt) 메커니즘

이번 Unit에서 wait/notify 를 봤다면, 다음은 인터럽트 메커니즘.

  • interrupt() / isInterrupted() / interrupted()
  • InterruptedException
  • 인터럽트 플래그
  • 협력적 종료

Phase 6 진행 상황

🚀 Phase 6 — 스레드 간 협력
  ✅ Unit 6.1 생산자-소비자 문제
  ✅ Unit 6.2 wait()과 notify() ← 여기
  ⏭ Unit 6.3 인터럽트 메커니즘
  ⏭ Unit 6.4 yield()

4주차 누적 진행

✅ Phase 1~5 (21 Unit, 1차 정점 완료)
🚀 Phase 6 — 스레드 협력 (2/4 진행)

총: 23/35 Unit
profile
Software Developer

0개의 댓글