Object.wait()WAITING )한다.WAITING ) 상태로 전환한다. 이 메서드는 현재 스레드가 synchronized 블록이나 메서드에서 락을 소유하고 있을 때만 호출할 수 있다. 호출한 스레드는 락을 반납하고, 다른 스레드가 해당 락을 획득할 수 있도록 한다. 이렇게 대기 상태로 전환된 스레드는 다른 스레드가 notify() 또는 notifyAll() 을 호출할 때까지 대기 상태를 유지한다.Object.notify()synchronized 블록이나 메서드에서 호출되어야 한다. 깨운 스레드는 락을 다시 획득할 기회를 얻게 된다. 만약 대기 중인 스레드가 여러 개라면, 그 중 하나만이 깨워지게 된다.Object.notifyAll()synchronized 블록이나 메서드에서 호출되어야 하며, 모든 대기 중인 스레드가 락을 획득할 수 있는 기회를 얻게 된다. 이 방법은 모든 스레드를 깨워야 할 필요가 있는 경우에 유용하다.rt static util.ThreadUtils.sleep;
public class BoundedQueueV3 implements BoundedQueue {
private final Queue<String> queue = new ArrayDeque<>();
private final int max;
public BoundedQueueV3(int max) {
this.max = max;
}
@Override
public synchronized void put(String data) {
while (queue.size() == max) {
log("[put] 큐가 가득 참, 생산자 대기");
try {
wait(); // RUNNABLE -> WAITING, 락 반납
log("[put] 생산자 깨어남");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
queue.offer(data);
log("[put] 생산자 데이터 저장, notify() 호출");
notify(); // 대기 스레드, WAIT -> BLOCKED
}
@Override
public synchronized String take() {
while (queue.isEmpty()) {
log("[take] 큐에 데이터가 없음, 소비자 대기");
try {
wait();
log("[take] 소비자 깨어남");
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
String data = queue.poll();
log("[take] 소비자 데이터 획득, notify() 호출");
notify(); // 대기 스레드, WAIT -> BLOCKED
return data;
}
@Override
public String toString() {
return queue.toString();
}
}
synchronized 를 통해 임계 영역을 설정한다. 생산자 스레드는 락 획득을 시도한다.Object.wait() 을 사용해서 대기한다. 참고로 대기할 때 락을 반납하고 대기한다. 그리고 대기 상태에서 깨어나면, 다시 반복문에서 큐의 빈 공간을 체크한다.synchronized 를 통해 임계 영역을 설정한다. 소비자 스레드는 락 획득을 시도한다.Object.wait() 을 사용해서 대기한다. 참고로 대기할 때 락을 반납하고 대기한다. 그리고 대기 상태에서 깨어나면, 다시 반복문에서 큐에 데이터가 있는지 체크한다.RUNNABLE WAITING 상태가 된다.notify() 를 통해 큐에 저장할 여유 공간이 생겼다고, 대기하는 스레드에게 알려주어야 한다. 예를 들어서 큐에 데이터가 가득 차서 대기하는 생산자 스레드가 있다고 가정하자. 이때 notify() 를 호출하면 생산자 스레드는 깨어나서 데이터를 큐에 저장할 수 있다.
wait()로 대기 상태에 빠진 스레드는notify()를 사용해야 깨울 수 있다. 생산자는 생산을 완료하면notify()로 대기하는 스레드를 깨워서 생산된 데이터를 가져가게 하고, 소비자는 소비를 완료하면notify()로 대기하는 스레드를 깨워서 데이터를 생산하라고 하면 된다. 여기서 중요한 핵심은wait()를 호출해서 대기 상태에 빠질 때 락을 반납하고 대기 상태에 빠진다는 것이다. 대기 상태에 빠지면 어차피 아무일도 하지 않으므로 락도 필요하지 않다.

synchronized 임계 영역 안에서 Object.wait() 를 호출하면 스레드는 대기( WAITING ) 상태에 들어간다. 이렇게 대기 상태에 들어간 스레드를 관리하는 것을 대기 집합(wait set)이라 한다. 참고로 모든 객체는 각자의 대기 집합을 가지고 있다.BoundedQueue(x001) 구현 인스턴스의 락과 대기 집합을 사용한다.synchronized 를 메서드에 적용하면 해당 인스턴스의 락을 사용한다. 여기서BoundedQueue(x001) 의 구현체이다.wait() 호출은 앞에 this 를 생략할 수 있다. this 는 해당 인스턴스를 뜻한다. 여기서는BoundedQueue(x001) 의 구현체이다.
p1 이 락을 획득하고 큐에 데이터를 저장한다.notify() 를 호출하면 스레드 대기 집합에서 대기하는 스레드 중 하나를 깨운다.
wait() 를 호출한다.RUNNABLE WAITING 로 변경된다.notify() 를 통해 스레드 대기 집합에 신호를주면 깨어날 수 있다.
소비자 쓰레드가 락을 획득한 후,
notify() 를 호출해서 스레드 대기 집합에 이 사실을 알려준다.스레드대기열에 notify() 전달 후

notify() 신호를 받으면 대기 집합에 있는 스레드 중 하나를 깨운다.p3 는 대기 집합에서는 나가지만 여전히 임계 영역에BLOCKED 상태로 대기한다. 당연한 이야기지만 임계 영역 안에서 2개의 스레드가p3 : WAITING -> BLOCKEDwait() 를 호출한 부분 부터 실행된다. 락을 획득하면 wait() 이후의 코드를 실행한다.notify() 로 깨어난 생성자 쓰레드
BLOCKED -> RUNNABLEwait() 코드에서 대기했기 때문에 이후의 코드를 실행한다.data3 을 큐에 저장한다.notify() 를 호출한다. 데이터를 저장했기 때문에 혹시 스레드 대기 집합에 소비자가 대기하고 있다면 소비자를 하나 깨워야 한다. 물론 지금은 대기 집합에 스레드가 없기 때문에 아무 일도 일어나지 않는다.지금까지 살펴본 Object.wait() , Object.notify() 방식은 스레드 대기 집합 하나에 생산자, 소비자 스레드를모두 관리한다. 그리고 notify() 를 호출할 때 임의의 스레드가 선택된다. 따라서 앞서 살펴본 것 처럼 큐에 데이터가없는 상황에 소비자가 같은 소비자를 깨우는 비효율이 발생할 수 있다. 또는 큐에 데이터가 가득 차있는데 생산자가 같은 생산자를 깨우는 비효율도 발생할 수 있다. -> 스레드 기아 발생할 수 있음
이런 문제점을 해결 할 수있는 방법을 다음 블로그에 정리해보겠다.