본문 바로가기

Java/Java

[Java] Future.cancel() vs CompletableFuture.cancel()

[Java] Future.cancel() vs CompletableFuture.cancel()

CompletableFuture의 cancel은 우리의 예상과 달리 동작하고 있는 스레드를 완료 시킬 수 없다.

이 걸 모르고 개발하다가 버그가 발생한 적이 있는데, 

이번 글 에서는 CompletableFuture의 cancel과 Future의 cancel의 차이에 대해 알아보겠다.


Future 테스트

우선, Future를 이용해 비동기 서비스를 실행하고, 해당 스레드를 도중에 중단시킬수 있는지 확인해보겠다.

로직은 다음과 같다.

 

1. 10초동안 sleep 동작을 하는 스레드를 실행시킨다.

2. 해당 스레드에 강제로 InterrupedException을 발생시킨다. (cancel이용)

3. InterruptedException을 받은 스레드는 CountDownLatch의 countDownd()을 호출해 CountDownLatch의 count를 0으로 만든다.

4. awaitTillEnd(CountDownLatch) 함수에서 CountDownLatch가 1초안에 0이 되는지 확인한다. 된다면 true, 아니라면 false

 

Future.cancel() 테스트
스레드 종료를 1초동안 기다린다.

실제로 테스트를 실행시켜보자.

 

테스트 결과

테스트 결과, 테스트는 성공했고 스레드에 InterruptedException 발생시 "Count down 0" 로그도 잘 찍히는걸 확인할 수 있다.

 

Future.cancel() : https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/Future.html#cancel(boolean) 

 

Future (Java SE 11 & JDK 11 )

 

docs.oracle.com

문서 확인결과, InterrupedException을 발생시키면서 스레드를 종료시키며, cancel의 파라미터로 받는 mayInterruptIfRunning 파라미터에 따라 실행중인 스레드를 종료할지 말지 결정한다고 한다.

 

여기까지는 예상한대로의 동작이다.


CompletableFuture 테스트

이제 문제가 된 상황인 CompletableFuture를 이용해 똑같은 로직을 실행시켜보자.

로직은 마찬가지로 다음과 같다.

 

1. 10초동안 sleep 동작을 하는 스레드를 실행시킨다.

2. 해당 스레드에 강제로 InterrupedException을 발생시킨다. (cancel이용)

3. InterruptedException을 받은 스레드는 CountDownLatch의 countDownd()을 호출해 CountDownLatch의 count를 0으로 만든다.

4. awaitTillEnd(CountDownLatch) 함수에서 CountDownLatch가 1초안에 0이 되는지 확인한다. 된다면 true, 아니라면 false

 

CompletableFuture.cancel() 테스트
스레드 종료를 1초동안 기다린다.

이제 테스트를 실행시켜보자.

 

테스트 결과

테스트 결과 Thread.sleep(10000)에 의해 10초동안 sleep상태가 유지되다 [true, true, false]가 반환되며 테스트가 실패했다.

심지어, InterruptedException 발생시 찍을 로그인 Count down 0 또한 찍히지 않은 모습을 볼 수 있다.

 

CompletableFuture.cancel() :  https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/util/concurrent/CompletableFuture.html#cancel(boolean) 

 

CompletableFuture (Java SE 11 & JDK 11 )

Returns a new CompletableFuture that is completed when all of the given CompletableFutures complete. If any of the given CompletableFutures complete exceptionally, then the returned CompletableFuture also does so, with a CompletionException holding this ex

docs.oracle.com

문서에서 주목해야할 부분은 CompletableFuture.cancel() 메소드의 인자인 mayInterruptIfRunning 의 설명이다.

 

mayInterruptIfRunning - 이 파라미터는 이 구현(CompletableFuture)에서 아무런 영향이 없다. interrupt는 프로세스를 컨트롤 하는데 사용되지 않는다.


Future.cancel() vs CompletableFuture.cancel()

결론적으로 Future.cancel()는 실행중인 스레드에 InterruptedException을 발행시킬수 있지만, CompletableFuture.cancel()은 그러지 못한다. (다만, 두 메소드 모두 isDone(), isCancelled()는 올바르게 동작했다.)

두 동작의 차이를 알기위해 소스코드를 살펴보자.

 

Future.cancel() source code : https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/concurrent/FutureTask.java#L164

 

GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDK

Mirror of the jdk/jdk11 Mercurial forest at OpenJDK - GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDK

github.com

우선, Future.cancel()메소드 이다.

 

FutureTask.cancel() source code

Future.cancel()메소드의 if(mayInterruptIfRunning) 절을 보면, 현재 실행중인 스레드가 null이 아닐경우, 스레드에 interrupt()를 발생시키고 있다.

 

CompletableFuture.cancel() source code : https://github.com/AdoptOpenJDK/openjdk-jdk11/blob/master/src/java.base/share/classes/java/util/concurrent/CompletableFuture.java#L2395

 

GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDK

Mirror of the jdk/jdk11 Mercurial forest at OpenJDK - GitHub - AdoptOpenJDK/openjdk-jdk11: Mirror of the jdk/jdk11 Mercurial forest at OpenJDK

github.com

다음은 CompletableFuture.cancel()의 코드이다.

 

CompletableFuture.cancel() source code

CompletableFuture.cancel()메소드는 현재 실행중인 스레드에 대한 내용은 없으며, 호출시 postComplete()을 호출하는걸 알 수 있다.

(앞에서 읽었지만)심지어, mayInterruptIfRunning 파라미터는 사용되지도 않고있다.

 

즉, CompletableFuture.cancel()이 동작중인 스레드를 종료시키지 않는것은 버그가 아닌 결정이다.

 

이는, CompletableFuture가 애초에 스레드에 묶여서 동작하도록 설계되지 않았기 때문이라고 한다.


참고 : https://dzone.com/articles/completablefuture-cant-be

 

CompletableFuture Can't Be Interrupted - DZone

I wrote a lot about InterruptedException and interrupting threads already. In short if you call Future.cancel() not inly given Future will terminate pending...

dzone.com

 

'Java > Java' 카테고리의 다른 글

[Java] Json 기본 생성자 없이 역직렬화 하기  (0) 2023.10.24
[Java] 스레드와 Synchronized  (0) 2021.10.25
[Java] Java Heap Stack Static  (0) 2021.10.23
[Java] compareTo  (0) 2021.05.05
[Java] Wrapper Class  (0) 2021.05.02