[Java8] Chapter 6-3. Callable과 Future

 

✍️ Callable

Callable은 Runnable과 매우 유사하지만 작업의 결과를 반환할 수 있다.

 

정의한 Callable 작업을 수행하기 위해 submit 메서드의 인자로 넘겨준다.

재밌는 점은 submit 메서드는 Future라는 걸 반환한다. 즉, Callable이 반환하는 타입의 Future를 반환한다.

ExecutorService executorService = Executors.newSingleThreadExecutor();

Callable<String> hello = () -> {
	Thread.sleep(3000);
	
	return "hello world!";
};

Future<String> future = executorService.submit(hello);

 

get(), 작업의 결과를 가져오기

Callable이 반환하는 값을 실제로 Future의 get 메서드를 통해 얻어올 수 있다. 

System.out.println(future.get());

// hello world!

 

여기서 주의할 점은 get 메서드 이전까지는 Callable 작업을 기다리지 않고 쭉 실행이 된다.  

그러나 get 메서드를 만나는 순간 결과 Callable의 결괏값을 얻어올 때까지 기다린다. 일종의 Blocking call인 셈이다.

Future<String> future = executorService.submit(hello);

System.out.println("Start");

System.out.println(future.get());

System.out.println("End");

// Start
// 3초 기다림...
// hello world!
// End

 

isDone(), 작업의 상태 확인하기

그렇다면 마냥 기다릴 수만은 없지 않은가. Callable의 작업 상태를 알 수 있는 방법이 있을까?

상태를 알 수 있는 방법으로 isDone 메서드가 존재한다.

Future<String> future = executorService.submit(hello);

System.out.println(future.isDone());
System.out.println("Start");

System.out.println(future.get());

System.out.println(future.isDone());
System.out.println("End");

// false
// Start
// hello world!
// true
// End

 

cancel(), 작업 취소하기

진행 중인 작업을 취소하는 기능도 제공한다.

cancel 메서드에 true를 넘겨주면 현재 진행 중인 작업을 인터럽트 하면서 종료한다. 반면 false를 넘겨주면 작업을 기다린다.

그렇다면 false는 cancle이 아니지 않은가? 의문이 들 텐데, true, false 모두 작업의 결과를 get 할 수 없다는 제약이 걸린다. 동시에 isDone은 무조건 true를 반환한다.

Future<String> future = executorService.submit(hello);

System.out.println("Start");

future.cancel(false);

System.out.println(future.get());

System.out.println("End");
Exception in thread "main" java.util.concurrent.CancellationException

 

invokeAll(), 여러 작업 동시에 실행하기

Callable을 사용해서 여러 가지 작업들을 뭉쳐서 줄 수 있는데, invokeAll 메서드에 여러 작업들의 컬렉션을 넘겨주면 여러 작업을 한꺼번에 넘겨줄 수 있다.

한 가지는 흥미로운 특징은 invokeAll을 사용하면 모든 작업이 끝날 때까지 먼저 끝난 작업이 기다린단 점이다.

ExecutorService executorService = Executors.newFixedThreadPool(3);

Callable<String> hello = () -> {
	Thread.sleep(1000);
	
	return "hello";
};

Callable<String> world = () -> {
	Thread.sleep(2000);
	
	return "world";
};

Callable<String> bye = () -> {
	Thread.sleep(3000);
	
	return "bye";
};

List<Future<String>> futures = executorService.invokeAll(Arrays.asList(hello, world, bye));

for(Future<String> future : futures) {
	System.out.println(future.get());
}

// hello
// world
// bye

 

invokeAny(), 여러 작업 중에 하나라도 먼저 응답이 오면 끝내기

그런데, 이런 경우도 있을 수 있다. 세 대의 서버에 동일한 파일이 존재한다고 하자. 뭐 장애 극복을 위해서라든지... 어쩄든 세 서버에게 해당 파일을 보내달란 요청을 했을 때 동일한 파일임에도 invokeAll처럼 모든 작업을 기다릴 필요가 있을까? 

 

이런 상황에 사용하는 것이 invokeAny다. invokeAny의 한 가지 특징은 작업의 반환 값을 Future로 감싸지 않고 바로 반환하다는 점이다.

ExecutorService executorService = Executors.newFixedThreadPool(3);

Callable<String> hello = () -> {
	Thread.sleep(1000);
	
	return "hello";
};

Callable<String> world = () -> {
	Thread.sleep(2000);
	
	return "world";
};

Callable<String> bye = () -> {
	Thread.sleep(3000);
	
	return "bye";
};

String s = executorService.invokeAny(Arrays.asList(hello, world, bye));

System.out.println(s);

// hello

 

 

본 내용은 백기선님의 자바8 강의 내용입니다.

 

더 자바, Java 8 - 인프런 | 강의

자바 8에 추가된 기능들은 자바가 제공하는 API는 물론이고 스프링 같은 제 3의 라이브러리 및 프레임워크에서도 널리 사용되고 있습니다. 이 시대의 자바 개발자라면 반드시 알아야 합니다. 이

www.inflearn.com