CompletableFuture

함수형 프로그래밍 방식을 지원하는 비동기 병렬 프로그래밍 기법

 

  • 명시적 Thread Pool 선언이 필요없음
  • 함수형 프로그래밍 방식 지원
    • 간결한 코드, 높은 가독성
    • 각 병렬 Task 들의 손쉬운 결합, 예외처리 지원

 

 

 

 

supplyAsync(), runAsync()

 

CompletableFuture 는 메소드를 제공하여 직접 thread를 생성하지 않고 작업을 async로 처리할 수 있다.

 

runAsync(): 반환 값 X

static CompletableFuture<Void> runAsync(Runnable runnable)

 

supplyAsync(): 반환 값 O

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)

 

예제

 

수행하는데 각각 3초, 5초가 걸리는 메소드를 작성 후 비동기로 호출한다.

 

import org.junit.jupiter.api.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.concurrent.CompletableFuture;

import static java.util.concurrent.TimeUnit.MILLISECONDS;

public class CompletableFutureTest {

    Logger log = LoggerFactory.getLogger(getClass());

    public int 메소드_완료_3초_필요() {
        log.info("메소드_완료_3초_필요() 실행");
        try {
            Thread.sleep(3000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 3;
    }

    public int 메소드_완료_5초_필요() {
        log.info("메소드_완료_5초_필요() 실행");
        try {
            Thread.sleep(5000L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return 5;
    }


    @Test
    void test() {
        long start = System.currentTimeMillis();

        CompletableFuture<Integer> fut3 = CompletableFuture.supplyAsync(this::메소드_완료_3초_필요);
        CompletableFuture<Integer> fut5 = CompletableFuture.supplyAsync(this::메소드_완료_5초_필요);

        int b = fut5.join();
        log.info("fut5 걸린시간 {}s", MILLISECONDS.toSeconds((System.currentTimeMillis() - start)));

        long start2 = System.currentTimeMillis();
        int a = fut3.join();
        log.info("fut3 걸린시간 {}s", MILLISECONDS.toSeconds((System.currentTimeMillis() - start2)));

        long elapsed = System.currentTimeMillis() - start;
        log.info("[전체시간={}s] 결과는 a + b = {}", MILLISECONDS.toSeconds(elapsed), (a + b));
    }
}

 

join()을 사용해서 메소드 수행이 끝나기를 기다린 후 결과값을 받아오는 Blocking 방식의 테스트를 작성하였다.

join 메소드는 get 메소드와 달리 unchecked exception 을 발생시킨다.

 

Exception 관련 포스팅

https://e-una.tistory.com/29

 

Blocking 관련 포스팅

https://e-una.tistory.com/54

 

 

결과

1. 5초 메소드, 3초 메소드를 비동기로 병렬 실행

2. 5초 메소드의 결과값을 받아오는데에 5초 걸림

3. 3초 메소드는 이미 실행이 완료되었으므로 결과값 받아오는데 0초 걸림

4. 전체 소요 시간은 5초

5. 리턴값도 맞게 받아와짐

 

 

 

 

비동기 작업 완료 콜백

  • 메소드 체이닝으로 CompletableFuture에 후속 작업을 지정할 수 있다.
  • get(), join() 대신 콜백 함수를 구현하면 Async-Non-Blocking 하게 개선할 수 있다.

 

 

thenAccept()

CompletableFuture<Void> thenAccept(Consumer<? super T> action)

비동기 작업이 완료됐을 때 완료 결과를 소비함

--> 작업이 끝났다는 의미 (반환값이 없음)

 

 

thenApply()

<U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)

비동기 작업이 완료되었을 때, 결과 T를 새로운 값 U로 변환하는 함수 실행

--> 반환값 존재

 

 

exceptionally()

CompletableFuture<T> exceptionally(Function<Throwable, ? extends T> fn)

비동기 작업에서 예외가 발생했을 때, Throwable로 예외를 받아 결과값 T 생성

 

 

예제

@Test
    void test2() {

        CompletableFuture.supplyAsync(() -> {
            log.info("supplyAsync");
            return "hi!";
        })
        .thenApply(s -> {
            log.info("thenAccept: {}", s);
            return s + "안녕!";
        }).thenAccept(s -> log.info("thenApply: {}", s));

        log.info("다른 작업 수행 중~~");
    }

 

 

 

allOf(), anyOf()

 

 

allOf()

static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs)

 

동시에 여러개의 비동기 작업을 실행하고, 모든 작업이 완료되면 진행

리턴값이 Void이므로 thenApply()를 통해 결과를 반환받아야함

 

참고

https://wbluke.tistory.com/50

 

 

anyOf()

static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs)

 

동시에 여러개의 비동기 작업을 실행하고, 하나라도 작업이 완료되면 진행

복사했습니다!