메뉴 닫기

JAVA 쓰레드 생명주기와 상태별 동작 원리 완벽 정리


JAVA 쓰레드 생명주기와 상태별 동작 원리 완벽 정리

🚦 쓰레드의 흐름을 이해하면 자바 동시성이 쉬워집니다

안녕하세요.
자바로 멀티스레드 프로그래밍을 하다 보면 꼭 마주치게 되는 개념이 바로 쓰레드의 생명주기입니다.
쓰레드가 생성되고 실행되는 시점은 쉽게 이해되지만, 일시정지나 종료 상태로 전환되는 과정은 처음 접하는 분들에겐 다소 복잡하게 느껴질 수 있어요.
실제로 많은 개발자들이 쓰레드 상태에 따라 어떤 메서드가 호출되는지를 제대로 이해하지 못해 동기화 오류나 교착상태를 경험하곤 하죠.
이번 포스팅에서는 자바 쓰레드의 생명주기를 전체 흐름으로 한눈에 파악하고, 각 상태에서 어떤 메서드가 어떤 역할을 하는지 쉽게 정리해 드릴게요.
처음 자바 쓰레드를 배우는 분들부터 실무에서 쓰레드를 다뤄야 하는 분들까지 모두에게 유용한 내용이니 꼭 끝까지 읽어보세요.

이번 글에서는 자바 쓰레드의 생명주기를 기준으로 각 상태가 의미하는 바와, 상태 전환에 영향을 주는 주요 메서드들을 체계적으로 정리합니다.
특히 동시성 처리의 흐름을 이해하는 데 중요한 Runnable, Blocked, Waiting 상태의 차이점을 명확히 구분해드릴 예정이에요.
글의 후반부에서는 실전 코드 예제도 함께 소개하니, 직접 실행하며 연습해보면 더 큰 도움이 될 거예요.







🧵 쓰레드란 무엇인가요?

자바에서 쓰레드(Thread)란 하나의 프로그램 내에서 동시에 실행될 수 있는 작업의 흐름을 의미합니다.
운영체제는 CPU의 시간을 여러 프로세스와 쓰레드에 분배해 빠르게 번갈아가며 실행되도록 만들기 때문에, 우리는 마치 여러 작업이 동시에 처리되는 것처럼 느낄 수 있습니다.

자바 프로그램은 기본적으로 하나의 메인 쓰레드를 가지고 실행되며, 개발자는 필요에 따라 추가로 쓰레드를 생성해 병렬 처리를 구현할 수 있습니다.
예를 들어, 백그라운드에서 파일을 다운로드하거나, 실시간 데이터를 처리해야 할 때 별도의 쓰레드를 활용하면 애플리케이션의 응답성을 유지할 수 있습니다.

💡 프로세스 vs 쓰레드의 차이

프로세스(Process)는 실행 중인 프로그램 자체이며, 운영체제로부터 독립된 메모리 공간을 할당받습니다.
반면 쓰레드는 해당 프로세스 내에서 실행되는 작업 단위로, 코드, 데이터, 힙 메모리는 공유하지만 스택 메모리는 각자 따로 갖고 있습니다.

  • 🧩프로세스는 완전한 실행 단위이며 메모리를 독립적으로 소유합니다
  • 🧵쓰레드는 하나의 프로세스 안에서 생성되며 자원을 공유합니다
  • 🔄멀티쓰레드를 활용하면 동시에 여러 작업을 처리할 수 있습니다

이처럼 자바에서 쓰레드는 성능 최적화와 응답성 개선에 핵심적인 역할을 합니다.
하지만 잘못 사용하면 데이터 충돌이나 예기치 못한 버그를 유발할 수 있으니 생명주기와 상태 관리를 정확히 이해하는 것이 중요합니다.


🚀 쓰레드 생명주기 단계별 설명

자바의 쓰레드는 크게 5가지 상태로 구분되는 생명주기(life cycle)를 따릅니다.
각 상태는 자바의 Thread.State 열거형으로 정의되어 있으며, 개발자가 쓰레드를 제어할 수 있는 흐름의 단위를 의미합니다.

  • 🍼NEW – 쓰레드가 생성되었지만 아직 시작되지 않은 상태
  • 🏃RUNNABLE – 실행 준비가 되어 CPU를 기다리는 상태
  • ⏸️WAITING / TIMED_WAITING – 다른 쓰레드의 작업을 기다리거나 일정 시간 정지된 상태
  • 🚧BLOCKED – 동기화 자원을 기다리며 대기하는 상태
  • 🛑TERMINATED – 쓰레드의 작업이 종료되어 더 이상 실행되지 않는 상태

🌀 상태 전이는 어떻게 일어날까요?

각 상태는 쓰레드가 실제로 호출하는 메서드에 따라 전환됩니다.
예를 들어 start()를 호출하면 NEW → RUNNABLE 상태로 전이되고, sleep(), join() 등은 TIMED_WAITING 또는 WAITING 상태를 유발합니다.
또한 synchronized 블록에 접근하려는 순간 다른 쓰레드가 해당 자원을 점유 중이면 BLOCKED 상태가 됩니다.

💬 쓰레드는 항상 RUNNABLE 상태에서 실행 대기 중이며, CPU가 할당되어야만 실제로 실행됩니다.

이처럼 자바에서 쓰레드의 생명주기는 정해진 흐름이 있으며, 각 상태별로 적절한 메서드 호출을 이해하는 것이 중요합니다.
다음 섹션에서는 상태 전환을 유발하는 핵심 메서드들을 하나씩 살펴보겠습니다.







🛠️ 상태 전환에 사용되는 주요 메서드

자바 쓰레드는 다양한 메서드를 통해 상태가 전환되며, 이 흐름을 명확히 이해해야 안정적인 멀티쓰레드 프로그래밍이 가능합니다.
아래는 상태 전환을 유도하는 주요 메서드들과 그 기능을 정리한 내용입니다.

메서드 설명
start() NEW → RUNNABLE로 전환하며 쓰레드 실행을 시작
sleep(ms) 지정 시간 동안 TIMED_WAITING 상태로 진입
join() 다른 쓰레드의 종료를 기다리며 WAITING 상태로 전환
wait() monitor lock을 해제하고 WAITING 상태에 진입
notify() / notifyAll() 대기 중인 쓰레드를 깨워 RUNNABLE로 전환 가능
interrupt() WAITING 또는 TIMED_WAITING 상태의 쓰레드를 깨움

💎 핵심 포인트:
start()는 단 한 번만 호출할 수 있으며, 이미 종료된 쓰레드는 다시 시작할 수 없습니다.

이 외에도 yield(), setDaemon(true) 같은 메서드들도 쓰레드 실행 방식에 영향을 줄 수 있습니다.
하지만 상태 전이를 직접 일으키는 것은 위 표에 정리된 메서드들이며, 대부분의 동시성 제어는 이들 중심으로 이뤄집니다.


🔄 쓰레드 상태 전이 흐름도

쓰레드의 상태 전이는 정해진 생명주기 순서에 따라 흐릅니다.
이를 도식화하면 쓰레드가 어떤 메서드 호출을 통해 어느 상태로 이동하는지를 한눈에 이해할 수 있어요.

🗺️ 상태 전이 흐름 요약

  • 🍼NEW 상태에서 start()를 호출하면 RUNNABLE 상태로 전환
  • 🏃RUNNABLE은 실제 실행 중이거나 실행 대기 중인 상태
  • sleep(), join(), wait() 등 호출 시 WAITING 또는 TIMED_WAITING 상태로 이동
  • 🚧synchronized 진입 실패 시 BLOCKED 상태로 전이
  • 🛑run() 메서드 종료 또는 예외 발생 시 TERMINATED 상태로 진입

💎 핵심 포인트:
쓰레드는 BLOCKED와 WAITING을 명확히 구분해야 합니다. BLOCKED는 lock 확보 대기, WAITING은 다른 쓰레드의 동작을 기다리는 상태입니다.

이처럼 쓰레드 상태는 단순히 실행/정지의 개념을 넘어서, 다양한 비동기 흐름 속에서의 논리적 대기 상태를 표현합니다.
실제 코드를 작성할 때 이 흐름을 잘 이해하고 설계해야 교착상태(deadlock)불필요한 성능 저하를 방지할 수 있어요.







💻 실습 예제: Thread 상태 추적

쓰레드 생명주기를 제대로 이해하려면 직접 코드를 실행해보는 것이 가장 좋습니다.
아래는 자바의 Thread 상태 변화를 출력하는 간단한 실습 예제입니다.
이 코드를 통해 상태가 어떻게 바뀌는지 콘솔에서 확인할 수 있어요.

CODE BLOCK
public class ThreadStateExample {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread(() -> {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        System.out.println("1. 상태: " + t.getState()); // NEW
        t.start();
        System.out.println("2. 상태: " + t.getState()); // RUNNABLE

        Thread.sleep(500);
        System.out.println("3. 상태: " + t.getState()); // TIMED_WAITING

        t.join();
        System.out.println("4. 상태: " + t.getState()); // TERMINATED
    }
}

💎 핵심 포인트:
getState() 메서드를 사용하면 실행 중인 쓰레드의 현재 상태를 실시간으로 확인할 수 있습니다.

실제로 위 코드처럼 Thread.sleep()join()을 적절히 조합하면 RUNNABLE → TIMED_WAITING → TERMINATED 흐름을 쉽게 추적할 수 있어요.
이런 연습을 통해 생명주기의 개념이 자연스럽게 체득됩니다.

추가로, BLOCKED 상태는 synchronized 블록에 여러 쓰레드가 동시에 접근할 때 테스트해볼 수 있어요.
별도의 연습 예제도 직접 구성해보면 쓰레드의 작동 방식이 훨씬 더 명확해질 거예요.


❓ 자주 묻는 질문 (FAQ)

Thread의 start()와 run()의 차이점은 무엇인가요?
start()는 새로운 쓰레드를 생성해 run()을 호출하지만, run()을 직접 호출하면 일반 메서드처럼 동기적으로 실행되어 멀티쓰레딩이 발생하지 않습니다.
쓰레드 상태 중 BLOCKED와 WAITING은 어떻게 다른가요?
BLOCKED는 synchronized 블록에 들어가려다 lock을 얻지 못해 대기하는 상태이고, WAITING은 다른 쓰레드의 작업이 완료되기를 기다리는 상태입니다.
TIMED_WAITING 상태는 어떤 메서드에서 발생하나요?
sleep(), join(timeout), wait(timeout) 등 시간 제한이 있는 대기 메서드에서 TIMED_WAITING 상태가 발생합니다.
쓰레드 상태를 확인할 수 있는 방법은?
Thread 클래스의 getState() 메서드를 통해 현재 쓰레드의 상태를 확인할 수 있습니다.
하나의 쓰레드에서 여러 번 start()를 호출할 수 있나요?
아니요. start()는 한 번만 호출 가능하며, 이미 종료된 쓰레드를 다시 시작하면 IllegalThreadStateException이 발생합니다.
쓰레드가 TERMINATED 상태가 되는 조건은?
run() 메서드의 실행이 정상 종료되거나 예외가 발생하면 쓰레드는 TERMINATED 상태가 됩니다.
쓰레드를 안전하게 종료하는 방법은 무엇인가요?
volatile 변수나 interrupt() 메서드를 활용해 종료 플래그를 체크하며 run() 내부에서 안전하게 빠져나오도록 설계하는 것이 일반적입니다.
쓰레드에서 동기화가 필요한 이유는?
여러 쓰레드가 공유 자원에 동시에 접근하면 데이터 일관성이 깨질 수 있으므로 synchronized 키워드를 통해 동기화를 보장해야 합니다.



🧠 쓰레드 생명주기를 이해하면 동시성이 보인다

이번 글에서는 자바에서 가장 기본이자 핵심 개념 중 하나인 쓰레드 생명주기에 대해 단계별로 알아보았습니다.
쓰레드는 NEW → RUNNABLE → WAITING/BLOCKED → TERMINATED 순으로 흐르며, 각 상태는 특정 메서드에 의해 전이됩니다.
이 흐름을 이해하면 동시성 프로그래밍에서 흔히 발생하는 교착상태나 응답 지연 문제를 방지할 수 있습니다.

또한 실전 코드 예제를 통해 getState()로 상태를 추적하는 방법도 익혔습니다.
이처럼 직접 실행해보며 연습하면 쓰레드가 눈에 보이듯 익숙해질 거예요.
자바 멀티쓰레드 프로그래밍을 본격적으로 시작하고자 한다면, 쓰레드의 생명주기를 정확히 이해하고 상태 전이를 자연스럽게 떠올릴 수 있어야 합니다.
그만큼 중요한 주제이니 꼭 여러 번 반복해서 학습해보세요.


🏷️ 관련 태그 : 자바쓰레드, 자바멀티스레딩, Thread생명주기, 동시성처리, synchronized, wait notify, 자바코딩기초, 자바입문, JavaThread, 상태전이