자바에서 Runnable과 Callable은 모두 함수형 인터페이스(Functional Interface)로, 이 인터페이스를 구현한 클래스는 별도의 스레드에서 실행되도록 설계되었습니다. 두 인터페이스는 멀티스레딩 환경에서 작업을 정의한다는 공통점이 있지만, 몇 가지 중요한 차이점이 존재합니다.
새로운 스레드를 시작하는 두 가지 방법
Runnable 인터페이스를 사용하면 새로운 스레드를 시작할 수 있으며, 자바에서 새 스레드를 생성하는 방법은 크게 두 가지입니다.
- Thread 클래스 상속: Thread 클래스를 상속받아 run() 메서드를 오버라이드하는 방식
- Runnable 인터페이스 구현: Runnable 인터페이스를 구현한 후 Thread 객체에 전달하는 방식
반면, Thread 클래스에는 Callable을 매개변수로 받는 생성자가 없기 때문에, Callable 작업을 실행하려면 반드시 ExecutorService 클래스를 사용해야 합니다.
Runnable과 Callable 주요 차이점 비교표
| 번호 | 구분 | Runnable | Callable |
|---|---|---|---|
| 1 | 패키지(Package) | java.lang 패키지에 속함 | java.util.concurrent 패키지에 속함 |
| 2 | 스레드 생성 | Runnable을 매개변수로 전달하여 스레드 생성 가능 | Callable을 매개변수로 전달하여 직접 스레드 생성 불가 |
| 3 | 반환 타입 | 어떠한 값도 반환하지 않음(void) | 작업 수행 결과를 반환할 수 있음 |
| 4 | 핵심 메서드 | run() 메서드 사용 | call() 메서드 사용 |
| 5 | 대량 작업 실행 | 작업의 일괄 실행에 사용할 수 없음 | invokeAll() 메서드를 호출하여 여러 작업을 일괄 실행 가능 |
Runnable 예제 코드
public class RunnableExample implements Runnable {
public void run() {
System.out.println("Hello from a Runnable!");
}
public static void main(String args[]) {
(new Thread(new RunnableExample())).start();
}
}Callable 예제 코드
import java.util.concurrent.Callable;
public class Task implements Callable {
@Override
public String call() throws Exception {
System.out.println("In call");
String name = "test";
return name;
}
}
public class Main {
public static void main(String args[]) throws InterruptedException, ExecutionException {
ExecutorService services = Executors.newSingleThreadExecutor();
Future<?> future = services.submit(new Task());
System.out.println("In Future Object" + future.get());
}
}정리
간단히 말해, 결괏값이 필요 없는 단순한 작업이라면 Runnable을, 작업 결과를 받아야 하거나 예외 처리가 필요한 작업이라면 Callable을 사용하는 것이 적합합니다. 특히 Callable은 Future 객체를 통해 비동기 작업의 결과를 조회할 수 있다는 강력한 장점이 있습니다.