[Java][Thread] 쓰레드 클래스의 start()와 run() 메서드

2022. 6. 30. 11:03JAVA/Language

1. 왜 쓰레드 클래스의 run() 메서드 대신 start() 메서드를 호출해야 하는가?

일반적으로 쓰레드 인스턴스를 생성하면 쓰레드 클래스에 재정의되어 있는 run() 메서드를 호출합니다. 하지만 실제로 클라이언트에서 호출할때는 start() 메서드를 호출하여 run() 메서드를 실행시킵니다.

class MyThread implements Runnable{
	public void run(){
    	...
    }
}
Thread t1 = new Thread(new MyThread());
t1.start();

 

run() 메서드 대신 start() 메서드를 호출해야 하는 이유는 run() 메서드를 호출하면 호출 스택(callstack)이 생성되지 않고  쓰레드가 독립적으로 수행되지 않습니다. 그러나 start() 메서드 호출시 호출 스택이 생성되기 때문에 각각의 쓰레드가 독립적으로 수행될 수 있습니다. 여기서 호출 스택이란 호출한 메서드들을 스택 자료구조에 삽입하고 메서드가 종료되면 삭제하는 것입니다.

 

모든 쓰레드는 독립적인 작업을 수행하기 위해서 자신만의 호출스택을 필요로 하기 때문에, 새로운 쓰레드를 생성하고 실행시킬 때마다 새로운 호출스택이 생성되고 쓰레드가 종료되면 작업에 사용된 호출 스택은 소멸됩니다.

 

다음은 main 메서드에서 새로운 쓰레드를 생성하고 start() 메서드를 호출하여 호출 스택을 생성하고 그 스택에 run() 메서드를 올리는 과정을 표현합니다.

  1. main 메서드에서 쓰레드의 start()를 호출
  2. start()는 새로운 쓰레드를 생성하고, 쓰레드가 작업하는데 사용될 호출스택을 생성
  3. 새로 생성된 호출스택에 run()이 호출되어, 쓰레드가 독립된 공간에서 작업을 수행
  4. 이제는 호출스택이 2개이므로 스케줄러가 정한 순서에 의해서 번갈아 가면서 실행됨

 

다음 예제는 새로 생성한 쓰레드에서 고의로 예외를 발생시켜 예외가 발생한 당시의 호출스택을 출력하는 예제입니다.

public class MyThread extends Thread{

	@Override
	public void run() {
		throwException();
	}
	
	public void throwException() {
		try {
			throw new Exception();
		}catch(Exception e){
			e.printStackTrace();
		}
	}
	
	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		t1.start();
	}
}
java.lang.Exception
	at ch13.ex_02_callstack_start.MyThread.throwException(MyThread.java:19)
	at ch13.ex_02_callstack_start.MyThread.run(MyThread.java:14)

위 실행결과를 보면 호출 스택의 첫번째 메서드가 main 메서드가 아니라 run 메서드인 것을 확인할 수 있습니다. 이는 예외가 발생할 당시에 main 쓰레드 위에 run 메서드가 실행된 것이 아닌 main 쓰레드와 독립적인 하나의 호출스택에서 run 메서드가 올라간 것을 확인할 수 있습니다. 이 설명을 그림으로 표현하면 다음과 같습니다.

다음 예제는 이전 예제와는 다르게 start() 메서드를 호출하지 않고 run() 메서드를 호출하여 예외가 발생할 당시의 호출 스택을 출력하는 예제입니다.

public class MyThread extends Thread{

	@Override
	public void run() {
		throwException();
	}
	
	public void throwException() {
		try {
			throw new Exception();
		}catch(Exception e){
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		MyThread t1 = new MyThread();
		t1.run();
	}

}
java.lang.Exception
	at ch13.ex_03_callstack_run.MyThread.throwException(MyThread.java:19)
	at ch13.ex_03_callstack_run.MyThread.run(MyThread.java:14)
	at ch13.ex_03_callstack_run.MyThread.main(MyThread.java:27)

위 실행 결과를 보면 호출 스택의 제일 밑에 있는 메서드가 main 메서드 인것을 확인할 수 있습니다. 이 결과의 의미는 run 메서드와 main 메서드가 각각 독립적으로 실행되지 않고 run 메서드가 종료될 때까지 main 메서드는 대기하게 됩니다. 그림으로 표현하면 다음과 같습니다.

 

정리하며

쓰레드 인스턴스의 run() 메서드와 start() 메서드 호출의 차이는 run() 메서드 호출시에는 호출 스택이 생성되지 않아 쓰레드가 독립적으로 수행되지 않고 start() 메서드 호출시에는 호출 스택이 새로 생성되어 run() 메서드를 새로 생성한 호출 스택에 넣고 쓰레드를 독립적으로 수행하도록 합니다.

 

References

source code : https://github.com/yonghwankim-dev/java_study/tree/main/ch13
[도서] Java의 정석, 남궁 성 지음