
클래스와 인터페이스 이름만 봐도 다른거아냐?라고 할 수 있지만, 조금 헷갈리는 부분이 있어 정리하게 되었다.
Oracle의 Thread 공식문서에서 보면 아래와 같이 Runnable 인터페이스를 구현하고 있다.
그런데, 생성자에 들어갈 수 있는 인자를 보면 Runnable이 들어가 있다.
Runnable 인터페이스를 구현하고 있는데 생성자에 Runnable를 주입한다..? 🤔
이에 대해 설명하려면 우선 스레드를 생성하는 방법부터 알아야한다. 스레드를 생성하는 방법은 2가지가 있다.
1. Thread 클래스를 사용하여 쓰레드를 생성하는 것
Thread 클래스를 상속하고 run() 메서드를 오버라이드하여 원하는 작업을 수행한 후 start()메소드를 통해 스레드를 시작한다.
public class MyThread extends Thread {
public void run() {
// 스레드가 수행할 작업들 ...
}
public static void main(String[] args) {
MyThread thread = new MyThread();
thread.start();
}
}
2. Runnable 인터페이스를 구현하여 스레드를 생성하는 것
Runnable 인터페이스를 구현한 후, 이를 Thread 클래스의 생성자에 전달하여 스레드를 생성한다.
public class MyRunnable implements Runnable {
public void run() {
// 스레드가 수행할 작업들 ...
}
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
여기서 Thread의 생성자에 들어가는 Runnable은 당연하겠지만 Runnable 인터페이스의 구현체이다. 여기서 나의 궁금점은
Runnable를 구현하고 있는 Thread의 인자에 왜 Runnable를 넣는 것인가?
Runnable 인터페이스를 구현하면 해당 클래스의 인스턴스를 스레드로 실행할 수 있다. Runnable 인터페이스에는 run() 메서드가 정의되어 있는데, 이 메서드에 원하는 작업을 작성하면 스레드가 해당 작업을 실행할 수 있다. 즉, Runnable 인터페이스 구현체를 인자로 받으면 thread.start()를 했을 때 Runnable 인터페이스 구현체 내부의 run()메소드를 실행하는 것이다.
🤓 내 식대로 비유를 하자면, 스레드의 start()메소드가 턴테이블이라면, 스레드 또는 Runnable 인터페이스 구현체의 run()메소드는 LP판인 것이다.
Runnable 인터페이스를 구현하여 스레드를 생성하는 방법을 사용하면 여러 장점이 있다. Thread 클래스를 상속받으면 다른 클래스를 상속받을 수 없지만, Runnable 인터페이스를 구현하는 방법을 사용하면 다중 상속의 제약이 없어지므로 유연성이 향상된다. 또한, Runnable은 익명 객체 및 람다로 사용할 수 있지만 Thread는 별도의 클래스를 만들어야 하고 더 많은 자원이 필요하다. 그렇다고 Runnable를 사용하는 것이 무조건 좋냐?는 아니다. 상황에 따라 Thread 클래스를 직접 상속받아야 할 때도 있다.
하지만, 위와 같이 Thread와 Runnable을 직접 사용하는 방식들은 한계점이 존재한다. 이 방식들은 초기 자바에서 사용했던 방식으로 저수준이며 값의 반환이 불가능하다. 또한, 매번 스레드를 생성하고 종료하면서 오버헤드가 발생하고 관리가 어렵다. 이와 같은 문제점들을 개선시키기 위해 자바5부터 Executor, ExecutorService, ScheduledExecutionService와 Callable, Future가 등장하였고 자바8부터는 CompletableFuture가, 자바 19(정식으로는 21)부터는 Project Loom이라는 가상 스레드를 사용하는 방식이 도입되었다고 한다.