Java

[Java] Thread가 하나 생성되면 Stack도 하나 생성된다.

kimphoby 2025. 4. 18. 20:50

스레드의 동작 방식을 알기 전에, 일단 프로세스와 스레드가 뭔지 알아봅시다. 

프로세스

  • 프로그램은 실제 실행하기 전까지는 단순한 파일에 불과한다. 
  • 프로그램을 실행하면 프로세스가 만들어지고 프로그램이 실행된다. 
  • 이렇게 운영체제 안에서 실행중인 프로그램을 프로세스라고 한다. 
  • 프로세스는 실행 중인 프로그램의 인스턴스이다. 
  • 자바 언어로 비유를 하자면 클래스는 프로그램이고, 인스턴스는 프로세스이다. 

각 프로세스는 독립적인 메모리 공간을 갖고 있으며, 운영체제에서 별도의 작업 단위를 갖는다. 각 프로세스는 서로의 메모리에 직접 접근할 수 없다. (격리된 환경 +> 특정 프로세스에 심각한 장애가 발생하면 해당 프로세스만 종료되고, 다른 프로세스에 영향을 주지 않는다.) 

 

프로세스의 메모리 구성 

  • 코드 섹션 : 실행할 프로그램의 코드가 저장되는 부분 
  • 데이터 섹션 :  전역 변수 및 정적 변수가 저장되는 부분 
  • 힙 ( Heap ) :  동적으로 할당되는 메모리 영역
  • 스택 ( Stack ) : 메서드 (함수) 호출 시 생성되는 지역 변수와 반환 주소가 저장되는 영역 ( 스레드에 포함 )

스레드(Thread)

프로세스는 하나 이상의 스레드를 반드시 포함한다. 스레드는 프로세스 내에서 실행되는 작업의 단위이다. 한 프로세스 내에서 여러 스레드가 존재할 수 있으며, 이들은 프로세스가 제공하는 동일한 메모리 공간을 공유한다. 

 

스레드의 메모리 구성 

  • 공유 메모리 : 같은 프로세스의 코드 섹션, 데이터 섹션, 힙(메모리)은 프로세스 안의 모든 스레드가 공유한다. 
  • 개별 스택 : 각 스레드는 자신의 스택을 갖고 있다. 

정리하면 프로세스는 실행 환경과 자원을 제공하는 컨테이너 역할을 하고, 스레드는 CPU를 사용해서 코드를 하나하나 실행한다. 

Thread 하나가 생성되면 Stack도 하나 생성된다. 

<스레드 상속 코드>

package thread.start;

public class HelloThread extends Thread {

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ": run()");
    }
}

<스레드 실행 코드>

* main thread가 run() 메서드를 실행하는게 아니라, Thread-0 스레드가 run() 메서드를 실행한다. 

main thread는 단지 start() 메서드를 통해 Thread-0에게 실행을 지시할 뿐이다. 

만약 main에서 run() 함수를 직접 호출한다면, Thread-0가 아닌 main 스레드가 run()을 실행한다. 

 

package thread.start;
import thread.start.HelloThread;

public class HelloThreadMain {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());
        System.out.println(Thread.currentThread().getName() + ": main() start");

        System.out.println(Thread.currentThread().getName() + ": run 호출 전");
        HelloThread helloThread = new HelloThread();
        helloThread.start();
        System.out.println(Thread.currentThread().getName() + ": main() end");
        System.out.println(Thread.currentThread().getName() + ": run 호출 후");

    }
}

 

 

스레드 생성 전 

 

프로세스가 작동하려면 스레드가 최소한 하나는 있어야 한다. 그래야 코드를 실행할 수 있다. 자바는 실행 시점에 main이라는 이름의 스레드를 만들고 프로그램의 시작점인 main() 메서드를 실행시킨다. 

 

Thread-0 생성 후 

 

HelloThread 스레드 객체를 생성한다음에 start() 메서드를 호출하면 자바는 스레드를 위한 별도의 스택 공간을 할당한다. 

새로운 Thread-0 스레드가 사용할 전용 스택 공간이 마련되었다. 

Thread-0는 run() 메서드의 스택 프레임을 스택에 올리면서 run() 메서드를 시작한다. 

 

<실행 결과>

 

스레드간 실행 순서는 보장하지 않는다. 스레드는 동시에 멀티 태스킹 되기 때문에 스레드간 실행 순서는 매번 달라질 수 있다. 

 

Runnable(Interface)을 사용해서 여러 Thread 생성하기 

 

<Main 코드>

package thread.start;

import static util.MyLogger.log;

public class ManyThreadMainV1 {
    public static void main(String[] args) {
        log("main() : start");

        HelloRunnable runnable = new HelloRunnable();
        for(int i=0; i<100; i++){
            Thread thread = new Thread(runnable);
            thread.start();
        }

        log("main() end");
    }
}

 

<Runnable 구현체>

package thread.start;

public class HelloRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + ": run() runnable");
    }
}


runnable은 인터페이스로, Thread를 상속 받는게 아니기 때문에 더 가볍다. 상속은 상속받은 메서드를 많이 사용하지 않으면 굳이 필요하지 않을 것 같다. 그래서 실무에서는 보통 Runnable을 사용한다고 한다. 

어찌되었든, 아래 여러개의 쓰레드가 생성된 것을 볼 수 있다. 각 스레드가 생김과 동시에 각각의 스택 공간이 할당된다. 아래 그림에서는 하나의 Runnable 객체를 각 Thread가 공유하고 있는 상황이다 .

 

 

Runnable을 익명 클래스로 만든 버전, 람다로 만든 버전, 이너 클래스로 만든 버전 모두 테스트해보았다. 

https://github.com/dPwls0125/java-adv1/tree/main/src/thread/start

 

java-adv1/src/thread/start at main · dPwls0125/java-adv1

김영한의 실전 자바 - 고급 1편, 멀티스레드와 동시성. Contribute to dPwls0125/java-adv1 development by creating an account on GitHub.

github.com