
자바 멀티쓰레딩 프로그래밍에서 여러 쓰레드가 협력하여 하나의 목적을 달성해야 할 때, 우리는 단순히 '동기화(Synchronization)'를 넘어 쓰레드 간의 '통신(Communication)'이 필요하게 됩니다. 자바의 Object 클래스는 이를 위해 wait(), notify(), notifyAll()이라는 세 가지 핵심 메서드를 제공합니다. 이 메서드들은 쓰레드가 자원을 낭비하며 무한 루프를 도는 'Busy Waiting' 상태를 방지하고, 시스템 리소스를 극도로 효율적으로 사용할 수 있게 돕습니다. 본 글에서는 이 메서드들의 정확한 용도와 동작 메커니즘, 그리고 실무에서 주의해야 할 핵심 포인트를 상세히 설명하겠습니다.
1. 메서드별 역할 및 기능 정의
이 메서드들은 Thread 클래스가 아닌 Object 클래스에 정의되어 있습니다. 이는 자바의 모든 객체가 고유의 모니터(Monitor)를 가지고 있으며, 쓰레드 간의 통신이 이 모니터 락을 기반으로 이루어지기 때문입니다.
| 메서드 | 주요 용도 | 쓰레드 상태 변화 |
|---|---|---|
| wait() | 락을 반납하고, 다른 쓰레드가 통지할 때까지 대기한다. | RUNNABLE → WAITING |
| notify() | 대기 중인 쓰레드 중 임의의 하나를 깨운다. | WAITING → BLOCKED (락 획득 대기) |
| notifyAll() | 대기 중인 모든 쓰레드를 깨운다. (권장되는 방식) | WAITING → BLOCKED (락 획득 대기) |
2. 반드시 지켜야 할 사용 규칙
이 메서드들을 사용할 때는 JVM이 강제하는 두 가지 중요한 규칙이 있습니다.
- synchronized 블록 내에서만 호출: 호출하는 쓰레드는 반드시 해당 객체의 모니터 락을 소유하고 있어야 합니다. 그렇지 않으면
IllegalMonitorStateException이 발생합니다. - 루프(while) 내에서 wait 사용: 쓰레드가 깨어났을 때 조건이 여전히 유효한지 다시 확인해야 합니다. 이를 'Spurious Wakeup(허위 깨어남)' 방지라고 합니다.
3. 실무 예제: 생산자-소비자 패턴 (Sample Example)
공유 자원인 큐(Queue)를 두고 데이터를 생성하는 쓰레드와 소비하는 쓰레드가 어떻게 협력하는지 보여주는 예제입니다.
class DataQueue {
private String data;
private boolean empty = true;
// 소비자용 메서드
public synchronized String consume() {
while (empty) { // 데이터가 없으면 대기
try { wait(); } catch (InterruptedException e) {}
}
empty = true;
notifyAll(); // 생산자에게 데이터 비었음을 알림
return data;
}
// 생산자용 메서드
public synchronized void produce(String newData) {
while (!empty) { // 데이터가 아직 있으면 대기
try { wait(); } catch (InterruptedException e) {}
}
empty = false;
this.data = newData;
notifyAll(); // 소비자에게 데이터 준비됨을 알림
}
}
4. notify() 보다는 notifyAll()을 사용해야 하는 이유
notify()는 대기 중인 쓰레드 중 하나만 무작위로 깨웁니다. 만약 깨어난 쓰레드가 자신의 조건이 맞지 않아 다시 wait()에 빠지게 되면, 시스템 전체가 아무도 깨워주지 않는 데드락 상태와 유사한 '신호 상실' 현상을 겪을 수 있습니다. 반면 notifyAll()은 모든 쓰레드를 깨워 각자가 조건을 확인하게 하므로 훨씬 안전하고 논리적으로 견고합니다.
5. 현대적인 대안: Condition 객체
JDK 5부터는 java.util.concurrent.locks.Condition 인터페이스를 통해 wait/notify보다 더 세밀한 제어가 가능해졌습니다. 하나의 락에 대해 여러 개의 대기 집합(Wait Set)을 가질 수 있어, 생산자만 깨우거나 소비자만 깨우는 등의 작업이 가능합니다. 대규모 프로젝트에서는 이 방식을 더 선호합니다.
내용 출처 및 참고 자료
- Oracle Java Docs: Object Class - wait(), notify()
- Joshua Bloch, "Effective Java 3rd Edition" - Item 81
- Java Language Specification Chapter 17. Threads and Locks
'Language > Java' 카테고리의 다른 글
| [JAVA] 가시성 문제의 해결사, volatile 키워드의 완벽 이해와 실무 활용 (0) | 2026.01.21 |
|---|---|
| [JAVA] 자바 쓰레드 제어의 한 끗 차이 : sleep() vs wait() 완벽 분석 (0) | 2026.01.21 |
| [JAVA] 데드락(Deadlock)의 미궁 : 원인 분석 및 현명한 예방 전략 (0) | 2026.01.21 |
| [JAVA] 동기화의 핵심, synchronized 키워드 완벽 정복하기 (0) | 2026.01.21 |
| [JAVA] Thread 생명 주기 완벽 가이드 : NEW에서 TERMINATED까지 (0) | 2026.01.21 |