[Java] Thread가 하나 생성되면 Stack도 하나 생성된다.
스레드의 동작 방식을 알기 전에, 일단 프로세스와 스레드가 뭔지 알아봅시다.
프로세스
- 프로그램은 실제 실행하기 전까지는 단순한 파일에 불과한다.
- 프로그램을 실행하면 프로세스가 만들어지고 프로그램이 실행된다.
- 이렇게 운영체제 안에서 실행중인 프로그램을 프로세스라고 한다.
- 프로세스는 실행 중인 프로그램의 인스턴스이다.
- 자바 언어로 비유를 하자면 클래스는 프로그램이고, 인스턴스는 프로세스이다.
각 프로세스는 독립적인 메모리 공간을 갖고 있으며, 운영체제에서 별도의 작업 단위를 갖는다. 각 프로세스는 서로의 메모리에 직접 접근할 수 없다. (격리된 환경 +> 특정 프로세스에 심각한 장애가 발생하면 해당 프로세스만 종료되고, 다른 프로세스에 영향을 주지 않는다.)
프로세스의 메모리 구성
- 코드 섹션 : 실행할 프로그램의 코드가 저장되는 부분
- 데이터 섹션 : 전역 변수 및 정적 변수가 저장되는 부분
- 힙 ( 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