(Java8) CompletableFuture
1
2
3
4
runAsync(() -> {
Thread.sleep(500);
}, executor);
CompleatbleFuture에 대해
- https://medium.com/@chanhyeonglee/completable-future-가이드-part-1
- https://medium.com/@chanhyeonglee/completable-future-가이드-part-2
- https://medium.com/@chanhyeonglee/completable-future-가이드-part-3
thenCompose VS thenApply
- thenCompose VS thenApply
- thenCompose는 Future 다음에 또 다른 Future를 이어서 실행하게끔 연결할 때 사용
- thenApply는 Future 결과를 받았을 때 반환 전에 어떤 처리를 apply 할 때 사용
exception handling : exceptionally와 handle, whenComplete
- https://www.logicbig.com/tutorials/core-java-tutorial/java-multi-threading/completion-stages-exception-handling.html
- exceptionally는 해당 stage에 도달했을 때, 전달되는 exception이 있으면 실행되며
recover 하는데 사용됨.
- handle은 이전 stage에서 exception이 발생했든 안했든 실행됨. 전달된 exception이 null인지 체크를 통해서 발생했는지 안했는지 체크 가능.이 역시 recover 하는데 사용될 수 있음. 이후 stage가 이어서 진행된다.
- https://www.logicbig.com/tutorials/core-java-tutorial/java-multi-threading/completion-stage-when-complete.html
- whenComplete도 handle처럼 이전 stage에서 exception이 발생했든 안했든 실행되고, result와 exception 둘 다 받음.
- handle과의 차이점은, handle은 결과를 반환하니 반면whenComplete는 결과를 반환하지 않고 이전 stage 결과를 그대로 다음 stage로 pass한다는 점 . (마치 함수형의 peek 같은 느낌이랄까)
- 그래서 이후 stage에 exceptionally가 있는 경우 이를 실행하고 recover 하여 이어서 실행하도록 할 수 있음.
.get()과 exception 처리
- https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/CompletableFuture.html#get-long-java.util.concurrent.TimeUnit-
- .get()은 다음 네 가지 Exception을 던짐
- CancellationException - if this future was cancelled
- ExecutionException - if this future completed exceptionally
- InterruptedException - if the current thread was interrupted while waiting
- TimeoutException - if the wait timed out
- 여기서 ExecutionException은 Future를 실행하다 Exception이 발생했고 이게 처리가 안된 경우 발생함.
- 그래서 그냥 .get()을 어차피 try-catch로 감싸야 하니 Future 로직 실행하다 발생한 Exception을 여기서 같이 처리해 줄 수도 있다.
- 물론 exceptionally나 handle 등으로 따로 이벤트루프 스레드 상에서 처리하게끔 하는게 필요한 경우도 있고.
CompletableFuture의 complete 시점
- 비동기적으로 complete 되는거라 외부에서는 언제 complete될지 알 수 없고 isComplete 같은걸로 검사해야 함.
- 예를 들면 RedisTemplate을 CompletableFuture를 이용해 작성한 경우, hset이 언제 Complete될지 외부에서는 알 수 없음.
- hset같은 명령어를 강제로 기다리면 사실상 비동기로 작성하는 의미가 없다.
- .get()을 호출하면 그 때 명령어가 실행되는게 아니라, lettuce-nioEventLoop가 상황 봐서 redis에 명령어 날리고 complete 시킴.
- 이게 언제 될지는 모름. 그때그때 다르다.
- 언제 호출될지 모른다면 먼저 호출한 hset보다, 뒤에 호출한 hget이 먼저 실행되는 케이스도 있을 수 있는거 아냐? 라는 생각이 들 수 있는데,
- 물론 작업들을 queue에 쌓아서 처리할테니 그런 일은 없을 것 같기는 하지만 순서를 보장해준다는 얘기가 없으면 방어적으로 코딩하는게 더 좋기는 하다.
- 실제로 테스트해보면 set 명령어 실행이 지연되다가 hget 결과를 .get() 하면서 실행되기도 함.
Future, CompletableFuture, ListenableFuture 비교
Future
- Futures
Future<>
를 반환함으로써 callback 지옥에 빠지지 않을 수 있음- callback 지옥? “리턴값이 필요해서 callback 안에 callback…”
- 하지만 필요한 결과를 얻으려
future.get()
를 호출하여 외부 실행과 동기화해야 함- 이 때 Future 결과가 아직 준비 안되었다면 결과가 만들어 질 때 까지 blocking 된다.
- Future 들을 조합하는 등 복잡한 연산에 대한 지원이 미흡
CompletableFuture ( ListenableFuture )
- CompletableFuture (completion_stage)
- 괜찮은 방법이지만, 몇 가지 부족한 점이 있음.
- .supplyAsync()를 통해서 CompletableFuture 로직을 비동기적으로 실행할 수도 있고, .get()을 통해서 현재 스레드에서 동기적으로 실행할 수도 있음.
- 전자의 경우 결국 CompletableFuture 로직을 호출 스레드가 아니라 별도 스레드풀 (ForkJoinPool ) 에서 돌리겠다는 아이디어인데
- 시간이 오래 걸리는 blocking http request api를 CompletableFuture에서 실행한다고 가정해보자.
- 호출 스레드 기준으로 보면 non blocking이고 thenXXX로 결과를 수신하면서 비동기적으로 callback을 실행하게 되니 async이긴 하지만
- 스레드풀 ForkJoinPool의 스레드 1개는 blocking이 걸린 상태로 응답을 기다리고 있어야 하기 때문에 결국 blocking이 걸린다.
- (큰 차이점) 그리고 Mono는 data가 필요할 때 resolve되는 반면(subscribe), CompletableFuture는 내가 지금 당장 필요하든 필요하지 않든 일단 스레드풀에서 실행 해서 resolve한다. (= lazy execution이 불가)
- Spring 4 MVC에서는 JDK8부터 제공되는
CompletionStage
대신, 그 역할을 하는ListenableFuture
를 자체적으로 제공했음 (하위 호환성 때문) - 참고로
RedisFuture
와CompletableFuture
는 같은 것을 상속받지만 호환되지 않는 다른 타입이다. 그래서 toCompletableFuture()가 있음.
Reactive Programming (Mono, Flux)
이러한 문제점을 보완하기 위해 스프링에서는 리액티브 프로그래밍 지원을 위한 새로운 모듈을 구현하기로 했음
**https://stackoverflow.com/questions/54866391/mono-vs-completablefuture**
- Reactive Programming
- 앞서 얘기했듯이 Future와의 제일 큰 차이는 data가 필요할 때(=subscriber가 data를 consume할 준비가 되었을 때) resolve할 수 있다는 Back Pressure 개념이다.
- reactor-kotlin-extensions 모듈을 추가하면 CompletableFuture.toMono 로 변환 가능
- Mono를 안거치고 Coroutine으로 바로 변환하려면?CompletableFuture를 Kotlin Coroutine으로 이관하기
참고 ) WebFlux가 아닌 spring boot MVC 에서 메서드를 비동기로 만드는 방법?
@Async 애너테이션 -https://brunch.co.kr/@springboot/401
기타
- Promise / Future에 대한 개념 정리
- Spring 5에서는
WebClient
가 도입되어 논블로킹 통신을 지원하고 있음. - https://lettuce.io/core/5.1.3.RELEASE/api/io/lettuce/core/api/async/RedisAsyncCommands.html
This post is licensed under CC BY 4.0 by the author.