본문 바로가기
Language/Java

[JAVA] start()와 run() 메서드의 결정적 차이 : 왜 run()을 직접 호출하면 안 될까?

by Papa Martino V 2026. 1. 21.
728x90

start()와 run() 메서드
start()와 run() 메서드

자바 멀티쓰레드 프로그래밍을 처음 접하는 개발자들이 가장 흔하게 저지르는 실수 중 하나는 바로 쓰레드를 실행할 때 run() 메서드를 직접 호출하는 것입니다. 코드상으로는 아무런 에러가 발생하지 않고 원하는 로직이 실행되는 것처럼 보이지만, 이는 사실 '멀티쓰레딩'이 아닌 '단일 쓰레드' 환경에서 함수를 호출한 것에 불과합니다. 본 포스팅에서는 Java 가상 머신(JVM) 내부에서 start()run()이 어떻게 다르게 동작하는지, 그리고 왜 반드시 start()를 통해서만 쓰레드를 기동해야 하는지 그 기술적 배경을 심층적으로 파헤쳐 보겠습니다.

1. start()와 run()의 핵심 개념 차이

간단히 요약하자면, run()은 단순히 수행할 작업이 담긴 일반 메서드일 뿐이고, start()는 새로운 실행 흐름(Call Stack)을 생성하는 트리거입니다.

구분 start() 메서드 run() 메서드
주요 역할 새로운 쓰레드를 위한 호출 스택(Call Stack) 생성 쓰레드가 수행할 로직 실행 (단순 오버라이딩 대상)
실행 주체 별도의 새로운 쓰레드(New Thread) 현재 메서드를 호출한 쓰레드(Main 또는 기존 쓰레드)
반복 호출 단 한 번만 호출 가능 (재호출 시 IllegalThreadStateException 발생) 일반 메서드이므로 무제한 호출 가능
동작 방식 Native 메서드를 통해 OS에 쓰레드 생성 요청 후 run() 실행 단순히 구현된 메서드 코드 순차 실행

2. JVM 내부 동작 원리: 호출 스택(Call Stack)의 관점

자바에서 새로운 쓰레드를 실행한다는 것은 OS로부터 독립적인 메모리 공간인 호출 스택(Call Stack)을 할당받는다는 의미입니다. start() 메서드가 호출되면 내부적으로 다음과 같은 과정이 일어납니다.

  1. JVM이 새로운 호출 스택을 생성합니다.
  2. 새로운 스택 영역에서 run() 메서드가 실행될 수 있도록 환경을 조성합니다.
  3. 스케줄러에 의해 해당 쓰레드가 CPU를 점유하면 비로소 run()이 실행됩니다.

반면 run()을 직접 호출하면 새로운 스택이 생성되지 않습니다. 기존에 작업을 하던 메인 쓰레드의 스택 위에서 단순히 run()이라는 이름의 메서드를 실행할 뿐입니다. 결과적으로 병렬 처리는 발생하지 않습니다.

3. 실전 예제 코드 (Sample Example)

두 방식의 차이를 가장 명확하게 보여주는 예제입니다. currentThread().getName()을 통해 어떤 쓰레드가 코드를 실행하고 있는지 확인해 보세요.


class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("실행 중인 쓰레드 이름: " + Thread.currentThread().getName());
        System.out.println("run() 메서드 로직 수행 중...");
    }
}

public class ThreadTest {
    public static void main(String[] args) {
        MyThread t1 = new MyThread();

        System.out.println("=== Case 1: run() 직접 호출 ===");
        t1.run(); // 새로운 쓰레드가 생기지 않음

        System.out.println("\n=== Case 2: start() 호출 ===");
        t1.start(); // 새로운 쓰레드가 생성되어 병렬 실행됨
    }
}
        

실행 결과 분석: Case 1에서는 main 쓰레드가 출력되지만, Case 2에서는 Thread-0와 같은 새로운 이름의 쓰레드가 출력되는 것을 확인할 수 있습니다.

4. 독창적인 인사이트: IllegalThreadStateException의 비밀

start() 메서드는 쓰레드의 상태(State)가 NEW일 때만 호출 가능하도록 설계되어 있습니다. 한 번 실행이 종료된 쓰레드(TERMINATED)나 현재 실행 중인 쓰레드에 대해 다시 start()를 호출하면 자바는 즉시 예외를 발생시킵니다. 이는 쓰레드 객체의 '일회성'을 강조하는 중요한 설계 원칙입니다. 반면 run()은 상태와 무관하게 언제든 호출되므로, 예외 처리가 누락되어 논리적 오류를 범할 위험이 큽니다.

5. 마무리 및 요약

멀티쓰레드 환경에서 run()을 직접 호출하는 행위는 엔진을 켜지 않고 자동차를 손으로 밀고 가는 것과 같습니다. 비동기 처리와 병렬성을 확보하고 싶다면 반드시 start()를 호출해야 합니다. 이 작은 차이가 복잡한 서버 애플리케이션의 성능과 안정성을 결정짓는 첫걸음임을 잊지 마세요.

내용 출처: Oracle Java Documentation (Thread Class), Java Concurrency in Practice (Brian Goetz), Effective Java 3rd Edition

728x90