Java에서 스레드는 wait(), notify(), notifyAll() 메소드를 통해 서로 통신할 수 있습니다. 이 메소드들은 모두 Object 클래스에 정의된 final 메소드로, 반드시 synchronized 블록이나 메소드 내부와 같은 동기화(synchronized) 컨텍스트 안에서만 호출해야 합니다.
세 가지 메소드의 역할
wait() 메소드
wait() 메소드는 현재 실행 중인 스레드를 대기 상태로 전환합니다. 대기 중인 스레드는 다른 스레드가 같은 객체에 대해 notify() 또는 notifyAll() 메소드를 호출할 때까지 기다리게 됩니다.
notify() 메소드
notify() 메소드는 해당 객체의 모니터(monitor)를 기다리고 있는 대기 중인 스레드 하나를 깨웁니다. 어떤 스레드가 깨어날지는 JVM이 결정하므로 특정 스레드를 직접 지정할 수 없다는 점에 유의해야 합니다.
notifyAll() 메소드
notifyAll() 메소드는 해당 객체의 모니터에서 대기 중인 모든 스레드를 한꺼번에 깨웁니다. 깨어난 스레드들은 객체의 락(lock)을 획득하기 위해 서로 경쟁하게 되며, 락을 얻은 순서대로 실행을 재개합니다.
주의 사항
스레드는 wait() 메소드를 호출함으로써 객체의 모니터에서 대기 상태에 들어갑니다. 만약 현재 스레드가 해당 객체 모니터의 소유자가 아닌 상태에서 이 메소드들을 호출하면 IllegalMonitorStateException 예외가 발생하므로, 반드시 동기화된 영역 안에서 호출해야 합니다.
메소드 시그니처
wait() 메소드 구문
public final void wait() throws InterruptedException
notify() 메소드 구문
public final void notify()
notifyAll() 메소드 구문
public final void notifyAll()
예제 코드
아래 예제는 두 스레드가 wait()와 notify()를 사용해 통신하는 과정을 보여줍니다. 메인 스레드는 join() 메소드에서 running 플래그가 false가 될 때까지 대기하고, 백그라운드 스레드는 3초 후 running을 false로 설정한 뒤 notify()를 호출하여 대기 중인 스레드를 깨웁니다.
public class WaitNotifyTest {
private static final long SLEEP_INTERVAL = 3000;
private boolean running = true;
private Thread thread;
public void start() {
print("Inside start() method");
thread = new Thread(new Runnable() {
@Override
public void run() {
print("Inside run() method");
try {
Thread.sleep(SLEEP_INTERVAL);
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
synchronized(WaitNotifyTest.this) {
running = false;
WaitNotifyTest.this.notify();
}
}
});
thread.start();
}
public void join() throws InterruptedException {
print("Inside join() method");
synchronized(this) {
while(running) {
print("Waiting for the peer thread to finish.");
wait(); // 대기 상태 진입
}
print("Peer thread finished.");
}
}
private void print(String s) {
System.out.println(s);
}
public static void main(String[] args) throws InterruptedException {
WaitNotifyTest test = new WaitNotifyTest();
test.start();
test.join();
}
}
실행 결과
Inside start() method Inside join() method Waiting for the peer thread to finish. Inside run() method Peer thread finished.
추가 팁: while 루프로 조건을 확인하는 이유
예제의 join() 메소드에서 if가 아닌 while 루프로 조건을 검사한 것은 의도적인 설계입니다. wait()는 허위 기상(spurious wakeup)으로 인해 notify() 호출 없이도 깨어날 수 있으며, 여러 스레드가 같은 객체를 기다릴 때 먼저 깨어난 스레드가 조건을 변경했을 수도 있습니다. 따라서 깨어난 후 반드시 조건을 다시 확인하는 while 루프 사용이 권장됩니다.