pacakge java.util.concurrent;
public interface Callable<V>{
V call() throws Exception;
}
java.util.concurrent
에서 제공하는 기능이며 반환타입이 제네릭 V이므로 값을 반환할 수 있습니다
또한 throws Exception 예외가 선언되어 있기 때문에 해당 인터페이스를 구현하는 모든 메소드는
체크예외인 Exception과 하위 예외를 모두 던질 수 있습니다
public class Test{
public static void main(String[] args) throws ExecutionException, InterruptedException{
ExecutorService service = Executors.newFixedThreadPool(1);
Future<Integer> future = service.submit(new TaskCallable());
Integer result = future.get();
service.close();
}
static class TaskCallable() implements Callable<Integer> {
@Override
public Integer call(){
int value = 10;
return value;
}
}
}
위 예제코드에서 먼저 newFixedThreadPool(size)를 사용하면 ExecutorService를 편하게 생성할 수 있습니다
이어서 Callable을 구현하는 부분을 보면 숫자를 반환하기 때문에 제네릭 타입을 Integer로 선언했습니다
ExecutorService가 제공하는 submit()을 통해서 Callble을 작업으로 전달할 수 있습니다
TaskCallable 인스턴스는 블로킹 큐에 전달되고, 스레드 풀의 스레드중 하나가 작업을 실행합니다
작업의 처리 결과는 직접 반환되는 것이 아니라 Future라는 특별한 인터페이스를 통해 반환됩니다
Future.get()을 통해 call()이 반환한 결과를 받을 수 있습니다
참고로 Future.get()은 InterruptedException과 ExecutionException 체크 예외를 던집니다
요청하는 스레드가 결과를 받아야 한다면, Callable을 사용하는 방식이 Runnable을 사용하는 방식보다
훨씬 편리합니다
멀티스레드를 복잡하게 구현하기 보다는 싱글 스레드 방식으로 편하게 개발하는 느낌이 들 정도입니다
또한 구현상에서 내가 스레드를 생성하거나 join()으로 스레르들 제어하지도 않고,
Thread라는 코드도 없습니다
단순히 ExecutorService에 필요한 작업을 요청하고 결과를 받아서 쓰면 되는 이것이 바로
복잡한 멀티스레드를 매우 편리하게 사용할 수 있는 Excutor 프레임워크의 강점입니다
future.get()를 호출하는 요청 스레드는 해당 작업을 처리하는 스레드 풀의 스레드가 작업을 완료하거나
완료하지 않았을 때에 따라 처리 방식이 달라집니다
그리고 왜 결과를 바로 반환하지 않고 불편하게 Future라는 객체를 대신 반환하는 지에 대한 의문도 들것 입니다
이것을 알아보기 위해 Future에 대해 더 분석해볼 필요가 있습니다