본문 바로가기
JAVA

[Java] SingleThread, MultiThread[ run()을 바로 호출할 때 start() 대신]

by itstime0809 2024. 3. 13.
728x90

Thread

 Thread의 정의는 한 프로그램 내에서 작업의 주체 단위를 의미합니다. 1:N관계를 가질 수 있기 때문에, 한 프로그램 내에서 여러 thread를 소유할 수 있습니다.

 

start(), run()

 생각해 보면, Thread를 만들고, start(), run() 메서드의 차이점을 모르고 Thread를 사용하는 건 말이 안 되기 때문에 주제는 start() 메서드와 run() 메서드는 어떻게 실행되며, 왜 둘의 차이가 발생하는지 작성합니다.

 

Runnable Interface

 

위 사진은 Runnable 인터페이스 입니다. 스레드 프로그래밍을 하기 위해서 Thread 클래스를 상속받거나, Runnable 인터페이스를 구현한다는 것은 알고 있습니다. 하지만 어떤 Article에서는 이것을 이렇게 설명합니다. 

 

1. Thread 클래스를 직접 상속하게 되면, run()메서드의 구현 강제성이 없다.

2. Runnable 인터페이스를 구현하게 되면, run()메서드에 대한 구현 강제성이 있다.

 

Thread 클래스와 Runnable 인터페이스의 구조가 어떻게 되어 있는지 한 번도 살펴본 적이 없다면, 구현 강제성에 대한 개념이 모호할 수 있다고 생각합니다. 

 

우선 첫 번째와 두 번째 모두 맞는 설명이며, 그 이유는 Thread는 클래스이며, Runnable은 interface기 때문이고 여기서부터 설명이 시작됩니다. Runnable은 인터페이스기 때문에, abstract 메서드를 가지고 있습니다. 단순하게 run() 메서드 한 가지만 구현을 하도록 되어 있습니다. 추상메서드가 구현하는 클래스에게 메서드 구현의 강제성을 부여하는 특징을 갖고 있기 때문에, Runnable Interface를 구현한 클래스는 run() 메서드를 강제로 구현해야 합니다.

 

VScode

 

Thread는 extends하고 있는 ThreadTests_1 클래스 경우, run() 메서드를 Override 하지 않아도 문제가 없음을 확인할 수 있습니다. 반면에 Runnable 인터페이스를 구현하고 있는 ThreadTest_2는 클래스명에 빨간 줄이 그어진 것을 확인할 수 있습니다. 또 ThreadTest_2는 must implement abstract method Runnable.run()을 구현하라고 합니다.

 

그러면, 두 번째 문장은 납득이 갑니다. 하지만 첫 번째 문장이 납득이 가기 위해서, Thread가 어떤 구조로 되어 있는지 확인해 볼 필요가 있습니다.

 

Thread class

 

Thread 클래스의 내부 구현 모습니다. 자세히 살펴볼 부분은 implements Runnable입니다. Thread가 run()메서드를 강제하지 않을 수 있는 이유는 Runnable 인터페이스를 구현하고 있기 때문입니다. 즉 Thread가 구현의 강제성을 미리 적용받아서 Thread 클래스를 구현하는 자식 클래스에게 구현의 강제성을 사라지게 만든 것입니다.

 

interface를 바로 구현하게 되면, 추상 메서드로 인해 구현의 강제성이 자식 클래스에게 바로 영향을 미치지만, Thread 클래스처럼 클래스 내에서 미리 구현의 강제성을 적용한 다음, 적용된(Thread)를 구현하는 다른 자식 클래스에게는 영향을 미치지 않을 수 있습니다.

 

따라서, 두 번째 문장이 납득이 갑니다. 결국 Thread가 run()메서드를 Override하고 있기 때문에 강제성이 사라진 겁니다.

그럼 이제 두 차이를 알았으니, 이 두 메소드를 main method에서 호출했을 때, 실행결과는 singleThread, multiThread로 구분된다는 것을 알아야 할 것입니다.

 

singleThread, multiThread

 먼저 싱글스레드는 말 그대로 쓰레드 하나로 processing을 하는것을 의미하고, 멀티 쓰레드는 여러 개의 스레드가 여러 작업들을 처리하는 것을 의미합니다. 근데 run() 메서드를 직접 호출했을 때와, start() 메서드를 직접 호출했을 때, 결과 값이 다릅니다. 이것을 이해하기 위해서 몇 가지를 이해해야 합니다.

1. Thread는 실행 순서를 보장하지 않습니다.

2. run() 메서드는 싱글스레드 환경에서 실행됩니다.

3. start() 메서드는 멀티스레드 환경에서 실행됩니다.

 

첫 번째 Thread가 실행 순서를 보장하지 않습니다. 그렇다는 것은 각 스레드가 언제 어떤 시점에 실행될지는 Base on OS에 따라 다르다는 것입니다. 그렇기 때문에 순서대로 작성된 코드가 기대되는 값이 1 -> 2라면, 언제든지 2 -> 1이 될 수 있습니다.

 

두 번째 run() 메서드는 싱글 스레드 환경입니다. 이것은 start()가 멀티스레드인 이유와 함께 설명이 됩니다. run()메서드는 하나의 스레드에서 수행됩니다. 즉 만약 main method 내에서 run()메서드를 실행했다면, main method를 실행하는 주체 thread가 존재합니다. 그럼 그 thread에 의해서 run()메서드가 수행됩니다. 이것은 곧 run()메서드는 새로운 스레드를 생성하지 않는 것을 의미합니다. 따라서 run()메서드는 일반적인 normal method가 됩니다. main method내에서 호출되는 call stack에 쌓이게 되는 메서드가 되어 버립니다. 

 

세 번째 start() 메서드는 멀티 스레드 환경입니다. 이것은 start() 메서드가 새로운 스레드를 생성하기 때문입니다. 즉 새로운 스레드가 생성되어 start() 메서드의 내부 구현이 thread가 시작되면 group에 추가되고, 해당 스레드가 실행되게 되면 start0()메소드를 내부에서 호출합니다. start0()메소드는 native 메소드로 구현되어 있습니다.

 

그러므로 run()을 바로 호출한다는 것은 하나의 스레드 환경에서 실행되기 때문에 멀티 쓰레드 환경을 만들지 않고 run()메소가 우선적으로 실행되는 순차구조를 띄우게 됩니다. 반면 start()메소드는 새로운 쓰레드 생성으로 인해 multiThread환경을 만들게 되고, Thread 클래스를 상속받았다면, run() 메서드가 오버라이드 되기 때문에, 자식의 run()메소드가 호출되어 자식 클래스 내에 구현된 run()메소드가 실행됩니다.