Callable과 Runnable은 Java에서 멀티스레딩을 구현할 때 사용하는 두 가지 인터페이스로, 각각의 차이점과 활용법이 있습니다. 특히, 비동기 작업과 병렬 처리가 필요한 상황에서 이 두 인터페이스를 통해 간단하고 효율적인 스레드 작업을 구현할 수 있습니다.
아래에 Callable과 Runnable을 자세히 설명하겠습니다.
1. Runnable
Runnable은 Java 1.0부터 제공된 인터페이스로, 스레드에서 수행할 작업을 정의합니다. Runnable 인터페이스는 리턴값이 없으며 단순히 스레드를 실행할 때 필요한 로직을 정의할 수 있습니다.
Runnable 기본 구조
Runnable 인터페이스는 단 하나의 메서드, run()을 가집니다.
@FunctionalInterface
public interface Runnable {
void run();
}
• run() 메서드: 스레드에서 실행할 작업을 구현하는 메서드로, 리턴값이 없습니다.
Runnable 사용 예시
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("Runnable 작업 실행 중...");
}
}
// Runnable 사용
public class Main {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
}
}
주요 특징
• 리턴값이 없으며, 예외 처리도 메서드 내에서 직접해야 합니다.
• 단순한 작업(리턴값 불필요)에 적합하며, 결과값이 필요하지 않은 비동기 작업에 주로 사용됩니다.
• Thread 객체의 생성자에 직접 넘겨서 사용합니다.
2. Callable
Callable은 Java 1.5에서 java.util.concurrent 패키지와 함께 도입된 인터페이스로, 결과값을 반환할 수 있고 예외 처리가 가능한 비동기 작업을 수행할 때 사용됩니다.
Callable 기본 구조
Callable 인터페이스는 제네릭 타입을 사용하여 반환할 값의 타입을 지정할 수 있으며, call() 메서드를 가집니다.
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
• call() 메서드: 작업을 수행하고, 결과를 리턴하며 예외 처리가 가능합니다.
Callable 사용 예시
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "Callable 작업 완료";
}
}
// Callable 사용
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<String> result = executor.submit(new MyCallable());
// 결과값 가져오기
System.out.println("결과: " + result.get());
executor.shutdown();
}
}
주요 특징
• 리턴값이 있으며, Future 객체를 통해 작업 완료 후 결과를 받을 수 있습니다.
• 예외 처리가 가능합니다. call() 메서드에 예외를 던질 수 있습니다.
• 비동기 작업에서 결과값을 얻거나 예외 처리가 필요한 경우에 적합합니다.
3. Runnable과 Callable의 비교
비교 항목 | Runnable | Callable |
---|---|---|
도입 시기 | Java 1.0 | Java 1.5 (java.util.concurrent) |
메서드 | run() | call() |
리턴 타입 | void (리턴값 없음) | 제네릭 타입 (리턴값 있음) |
예외 처리 | run() 메서드 내에서 처리해야 함 | call() 메서드에서 예외를 던질 수 있음 |
주요 용도 | 간단한 비동기 작업 (리턴값 필요 없음) | 비동기 작업 + 결과값/예외 처리 필요할 때 |
사용 방법 | Thread 클래스의 생성자에서 사용 | ExecutorService.submit() 메서드로 사용 |
4. Runnable과 Callable의 실제 사용 예
Java에서는 ExecutorService를 활용하여 Runnable과 Callable을 함께 사용하여 다양한 비동기 작업을 수행할 수 있습니다. 아래는 두 인터페이스를 활용한 실제 예입니다.
Runnable 예제 (간단한 비동기 작업)
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class RunnableExample {
public static void main(String[] args) {
ExecutorService executor = Executors.newFixedThreadPool(2);
Runnable task = () -> System.out.println("Runnable 실행 중...");
executor.submit(task);
executor.shutdown();
}
}
Callable 예제 (결과값이 필요한 작업)
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class CallableExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService executor = Executors.newFixedThreadPool(2);
Callable<String> task = () -> {
Thread.sleep(1000);
return "Callable 실행 완료";
};
Future<String> result = executor.submit(task);
System.out.println("결과값: " + result.get()); // 작업 완료 후 결과값 가져오기
executor.shutdown();
}
}
5. Runnable과 Callable의 활용 시 주의사항
• Runnable에서 결과값이 필요한 경우: Runnable 인터페이스는 결과를 리턴하지 않기 때문에, 결과가 필요하면 전역 변수를 사용하거나 별도로 FutureTask와 같은 클래스를 사용하는 것이 좋습니다.
• Callable의 결과값 지연: Callable의 결과는 Future.get() 메서드를 호출하기 전까지 기다립니다. get() 호출 시, 작업이 완료될 때까지 블로킹되므로 성능에 주의해야 합니다.
• 예외 처리: Callable은 예외 처리가 가능하지만, Runnable은 메서드 내부에서 예외를 처리해야 하므로 필요에 따라 Callable을 선택하는 것이 좋습니다.
6. Runnable과 Callable을 혼합하여 사용하기
때로는 두 인터페이스를 함께 사용하여 단순 작업과 복잡한 작업을 각각 처리하는 경우가 있습니다. 예를 들어, 여러 Runnable 작업을 실행하면서, 특정한 결과가 필요한 경우에만 Callable을 사용하는 방식입니다.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
public class MixedExample {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executor = Executors.newFixedThreadPool(3);
// Runnable 작업 리스트
List<Runnable> runnableTasks = List.of(
() -> System.out.println("Runnable Task 1"),
() -> System.out.println("Runnable Task 2")
);
// Callable 작업 리스트
List<Callable<String>> callableTasks = new ArrayList<>();
callableTasks.add(() -> "Callable Task Result 1");
callableTasks.add(() -> "Callable Task Result 2");
// Runnable 실행
for (Runnable task : runnableTasks) {
executor.submit(task);
}
// Callable 실행 및 결과 확인
for (Callable<String> task : callableTasks) {
Future<String> result = executor.submit(task);
System.out.println("Callable 결과: " + result.get());
}
executor.shutdown();
}
}
설명
• Runnable 작업은 실행되지만 결과값을 리턴하지 않습니다.
• Callable 작업은 결과값을 리턴하며, Future.get()으로 결과를 확인할 수 있습니다.
결론
Runnable과 Callable은 각각 간단한 비동기 작업과 결과값이 필요한 작업에 유용하게 사용됩니다. Runnable은 단순히 실행만 하면 되는 작업에 적합하고, Callable은 결과값을 반환하거나 예외를 처리해야 할 때 활용하기 좋습니다.
두 인터페이스의 특징을 이해하고, 필요한 경우에 적합한 인터페이스를 선택하여 효율적인 비동기 작업을 구성할 수 있습니다.
'Spring Security' 카테고리의 다른 글
@Async (0) | 2024.10.29 |
---|---|
DelegatingSecurityContextCallable (0) | 2024.10.29 |
Tomcat Executor (0) | 2024.10.29 |
SecurityContext, SecurityContextHolder, 스레드 처리 방식 (1) | 2024.10.29 |
UserDetailsService, PasswordEncoder, AuthenticationProvider (0) | 2024.10.29 |