Java Thread 상태 정리: WAITING, BLOCKED, RUNNABLE

guts·2025년 3월 26일

1. Java Thread에는 어떤 상태들이 있을까?

Java의 Thread.State enum에 따르면, 스레드는 다음과 같은 6가지 상태를 가질 수 있다.

그리고 문서를 자세히 읽어 보면

RUNNUBLE도 OS의 다른 리소스 때문에 "기다릴 수 있다."라고 말하고 있다.

❓ "RUNNABLE도 기다린다고? WAITING은 또 뭐고, BLOCKED는 뭐가 다르지?"

이게 궁금해서 WAITING / BLOCKED / RUNNABLE 에 대한 부분을 정리해봤다.

2. 언제 어떤 상황에서 스레드가 어떤 상태가 되는가?

[1] WAITING: 다른 스레드의 "신호"를 기다릴 때

  • 스레드가 다른 스레드의 작업이 끝나기를 기다릴 때 진입
  • Object.wait(), Thread.join() 등과 같은 메서드를 호출할 때 이 상태가 됨
  • 명시적으로 대기 상태에 들어감 (개발자가 직접 호출)
  • notify() 같은 알림 없이는 깨어날 수 없다.

(참고로 java의 모든 인스턴스들은 모니터를 가지고 있다.)

synchronized (obj) {
    obj.wait(); // WAITING 상태로 진입
}

[2] BLOCKED: 락을 얻기 위해 대기 중일 때

  • synchronized 블록 또는 메서드에 진입하려고 할 때 이미 다른 스레드가 락을 가지고 있는 경우
  • 스레드는 락이 풀릴 때까지 BLOCKED 상태로 대기
  • 자바에서 자동으로 발생, 개발자가 직접 BLOCKED 상태로 만드는 코드는 없음
Thread t1 = new Thread(() -> {
    synchronized (lock) {
        // 락을 오래 잡고 있음
    }
});

Thread t2 = new Thread(() -> {
    synchronized (lock) {
        // t1이 락을 가지고 있으면 여기서 BLOCKED
    }
});

[3] RUNNABLE: 실행 가능한 상태 (하지만 실제 실행 중이 아닐 수도 있음)

  • start()가 호출된 후 스레드는 RUNNABLE 상태가 됨
  • CPU에서 실행 중이거나, CPU 스케줄을 기다리는 중
  • 기다리는 건 맞지만 락이나 알림 때문이 아님 → CPU 자원 때문임

3. 상태별 비교

상태기다림 이유깨어나는 조건
WAITING다른 스레드의 알림notify, join 종료 등
BLOCKED락을 가진 스레드락이 해제됨
RUNNABLECPU 스케줄 대기OS 스케줄러

⚠️WAITING → RUNNABLE이 아닐 수도 있다?

이 지점에서 놓치기 쉬운 아주 중요한 포인트 하나가 있다.
notifyAll()로 스레드를 깨운다고 해서 바로 RUNNABLE 상태가 되는 게 아니다!

번역: 깨어난 스레드는 현재 스레드가 이 객체의 잠금을 해제할 때까지 진행할 수 없습니다.

정확한 흐름은 다음과 같다.
1. A 스레드는 wait()을 호출해서 WAITING 상태에 진입
2. B 스레드는 notifyAll()을 호출해 A를 깨운다
3. A는 잠에서 깨어나지만, 락을 아직 못 얻었기 때문에 BLOCKED 상태로 전환
4. B가 synchronized 블록을 나가서 락을 반환하면
5. 그제서야 A는 락을 얻고 RUNNABLE 상태로 진입 후 실행

코드 예시로 확인해보자.

final Object lock = new Object();

Thread a = new Thread(() -> {
    synchronized (lock) {
        try {
            System.out.println("A: waiting...");
            lock.wait();  // → WAITING 상태
            System.out.println("A: resumed and running");  // 실행 전에 BLOCKED 상태 거침
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

Thread b = new Thread(() -> {
    synchronized (lock) {
        System.out.println("B: notifying...");
        lock.notifyAll();  // A를 깨움 → A는 BLOCKED 상태로 변경됨
        try {
            Thread.sleep(2000);  // 락을 계속 보유
            System.out.println("B: exiting synchronized block");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
});

이 예시에서 A는 notifyAll()로 깨어난 이후, 즉시 실행되지 않고, 락을 기다리며 BLOCKED 상태에 머물게 된다

4. 결론

WAITING, BLOCKED, RUNNABLE 세 가지 상태는 모두 "기다림"이라는 공통점이 있지만,
무엇을 기다리고 있는가? 에 따라 전혀 다른 의미와 동작을 갖고 있다.

  • WAITING은 다른 스레드의 알림을 기다리는 상태
  • BLOCKED는 모니터 락을 얻기 위해 기다리는 상태
  • RUNNABLE은 CPU 자원을 기다리는 실행 가능 상태

또한, notify()나 notifyAll()을 통해 깨워도 바로 RUNNABLE로 가는 게 아니라, 락을 다시 얻을 때까지 BLOCKED 상태에 머무른다는 점은 공식 문서를 보지 않으면 간과하기 쉬운 디테일이었다.

언젠가 프로젝트를 하다가 Thread dump를 볼 일이 있으면 도움이 되지 않을까... 싶다.

참고 자료

https://docs.oracle.com/javase/8/docs/api/index.html?java/lang/Thread.State.html

https://stackoverflow.com/questions/15680422/difference-between-wait-and-blocked-thread-states?utm_source=chatgpt.com

profile
가자

0개의 댓글