ComputableFuture란
- 자바에서 비동기(Asynchronous)를 가능케 하는 인터페이스이다.
자바에서는 Future를 통해서 어느정도의 비동기 프로그래밍이 가능하기는 했지만, 하기 힘든 일들이 많았다.
- Future를 외부에서 완료시킬 수 없다.
- 작업을 취소하거나,
get()
에 타임아웃을 설정할 수 없다. - 블럭킹 코드를 사용하지 않고서는 작업이 끝났을 때 콜백을 실행할 수 없다.
- 예외처리용
API
를 제공하지 않았다.
비동기로 작업 실행하기
비동기로 작업을 실행하는 방법은 두가지가 있다.
runAsync()
: 리턴값이 없는 경우 사용한다.supplyAsync()
: 리턴 값이 있는 경우 사용한다.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
System.out.println("hello " + Thread.currentThread().getName());
return "Hello";
});
System.out.println(future.get());
}
}
다음은 ComputableFuture
를 사용한 예제이다. get()
을 호출 했을 때 실행되게 된다.
자바 API
를 살펴보면 supplyAsync()
메서드는 다음과 같다.
- 새로운
ComputableFuture
를 반환한다.ForkJoinPool.commonPool()
에서 실행중인 태스트에 의해서 비동기적으로 실행된다. - 역시 새로운
ComputableFuture
를 반환하지만 차이점은ExecutorService
에서 태스크를 처리한다.
따라서 ForkJoinPool
을 사용하지 않는다면 다음과 같이 ExecutorService
에서 태스트가 처리되도록 할 수 있다.
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService executorService = Executors.newFixedThreadPool(4);
CompletableFuture<Void> future = CompletableFuture.supplyAsync(() -> {
System.out.println("hello " + Thread.currentThread().getName());
return "Hello";
}, executorService).thenRunAsync(() -> {
System.out.println(Thread.currentThread().getName());
}, executorService);
future.get();
executorService.shutdown();
}
}
콜백 제공하기
thenApply(Function)
: 리턴값을 받아서 다른 값으로 바꾸는 콜백thenAccept(Consumer)
: 리턴값을 또 다른 작업으로 처리하는 콜백 (리턴 없이)thenRun(Runnable)
: 리턴 값을 받지 않고 다른 작업을 처리하는 콜백
조합하기
thenCompose()
: 두 작업이 서로 이어서 실행하도록 조합하기
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello " + Thread.currentThread().getName());
return "Hello";
});
CompletableFuture<String> future = hello.thenCompose(Main::getWorld);
System.out.println(future.get());
}
private static CompletableFuture<String> getWorld(String message) {
return CompletableFuture.supplyAsync(() -> {
System.out.println("World " + Thread.currentThread().getName());
return message + " World";
});
}
}
thenCombine()
: 두 작업을 독립적으로 실행하고 둘다 종료했을 때, 콜백 실행
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
System.out.println("Hello " + Thread.currentThread().getName());
return "Hello";
});
CompletableFuture<String> world = CompletableFuture.supplyAsync(() -> {
System.out.println("World " + Thread.currentThread().getName());
return "World";
});
CompletableFuture<String> future = hello.thenCombine(world, (h, w) -> h + " " + w);
System.out.println(future.get());
}
}
다음과 같이 두 작업이 각각 실행하여, 둘다 종료되었을 때, 콜백이 실행되었다.
allOf()
: 여러 작업을 모두 실행하고 모든 작업 결과에 콜백 실행anyOf
: 여러 작업중에 가장 빨리 끝난 하나의 결과에 콜백 실행
예외 처리
exceptionally(Funciton)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
boolean throwError = true;
CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
if (throwError) {
throw new IllegalArgumentException();
}
System.out.println("Hello " + Thread.currentThread().getName());
return "Hello";
}).exceptionally(ex -> {
System.out.println(ex);
return "Error";
});
System.out.println(hello.get());
}
}
handle(BiFunction)
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
public class Main {
public static void main(String[] args) throws InterruptedException, ExecutionException {
boolean throwError = true;
CompletableFuture<String> hello = CompletableFuture.supplyAsync(() -> {
if (throwError) {
throw new IllegalArgumentException();
}
System.out.println("Hello " + Thread.currentThread().getName());
return "Hello";
}).handle((result, ex) -> {
if (ex != null) {
System.out.println(ex);
return "ERROR!";
}
return result;
});
System.out.println(hello.get());
}
}