본문 바로가기

개발/BACK

[JAVA] JAVA 8 구현 인터페이스 CompletableFuture를 통해 비동기 처리하기

728x90

 

백엔드 로직의 모든 부분을 동기 방식으로 처리하게 되면,

해당 결과가 도출될 때까지 무한정 대기해야한다.

 

심지어 처리한 로직 부분이 대규모 트래픽을 다루는 API라면 스레드 설정에 따라

Exception을 내뱉거나 스레드 락에 걸릴 수 있다.

동기적 통신 ( 위 ) 와 비동기적 통신 ( 아래 )

 

 

위 이미지에서 보듯이, 동기적으로 데이터를 처리할 때는 1~4번의 스레드가 순차적으로 처리되며

작업이 종료되어야 다음 스레드의 처리 순서가 온다.

 

하지만, 요청된 결과를 받기 전에 다음 처리를 할 수 있는 비동기 처리를 통해 로직을 개선할 수 있다.

 

 

 

JAVA  8 버전 이하에서는 [ Future ] 라는 내장 인터페이스를 통해 비동기 처리를 지원했는데, 해당 인터페이스를 이용해서

구현하려면 스레드의 맥스 타임아웃을 설정하거나 구현 인터페이스 내에서 Exception을 처리하는 작업 등

사용하기 까다로운 경향이 있다.

 

이에, JAVA 8 버전에서 [CompletableFuture] 클래스를 사용할  수 있게 되고 

이전의 Future 보다 더 간결한 코드를 작성할 수 있도록 도와준다.

 

 

 

▼ CompletableFuture 클래스를 구현하여 비동기 처리 진행

 

        CompletableFuture<String> future = new CompletableFuture<>();

        int[] arr = {1,2,3,4,5};

        new Thread(() -> {
            System.out.println("thread start");
            for(int a : arr){
                if(a == arr[arr.length-1])
                    future.complete("배열의 마지막은 역시 "+ a);
            }
        }).start();


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

 

처리 작업 이후  CompletableFuture 내장 메소드 complete를 통해 담긴 값을 출력하는 예제다.

기본적으로 complete 메소드를 통해 future의 값을 꺼내 사용할 수 있다.

 

결과 ▼

 


▼ Thread 객체를 생성하지 않고 처리 진행

 

int[] arr = {1,2,3,4,5};
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("thread start");
            String data = "없네";
            for(int a : arr){
                if(a == arr[arr.length-1])
                    data = "배열의 마지막은 역시 "+ a;
            }
            return data;
        }).thenApply((result)->{
            System.out.println("result complete");
            return result + "0";
        });

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

 

해당 구문에서 supplyAsync 함수를 사용하여 Thread를 직접 생성하지 않고 비동기 처리를 진행했다.

 

해당 처리에서 thenApply의 역할은 비동기 처리의 결과를 return 할 때

리턴받은 결과를 처리하거나 가공할 때 사용된다.

 

결과 ▼

 


 

또한, 아래와 같이 에러 처리에 대한 handle 메소드도 제공된다.

 int[] arr = {1,2,3,4,5};
        CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
            System.out.println("thread start");
            String data = "없네";
            for(int a : arr){
                if(a == arr[arr.length-1])
                    data = "배열의 마지막은 역시 "+ a;
            }
            return data;
        }).thenApply((result)->{
            System.out.println("result complete");
            return result + "0";
        }).handle((result,throwable) -> {
            System.out.println("에러 발생 ! :" + throwable);
            return "에러 문구 ! : "+throwable;
        });

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

 


▼ 리스트 CompletableFuture 클래스를 선언해 사용 

List<String> arr = new ArrayList<>();
        arr.add("게임");
        arr.add("축구");
        arr.add("공부");
        arr.add("식사");
        arr.add("야구");
        arr.add("양치");

        List<CompletableFuture<String>> futureList =
                arr.stream()
                        .map(work -> CompletableFuture.supplyAsync(()->{
                            System.out.println(work + " 하는 중입니다.");
                            return work;
                        })).map(future -> future.thenApply(apply -> {
                            System.out.println(apply + " 끝냈습니다.");
                            return apply;
                        })).toList();

비동기 처리의 특징이 드러나면서 아래와 같이 뒤죽박죽 인간이 만들어질 수 있다.

 

결과 ▼

 

 

 

중규모 정도의 푸시 메세지 발송이나 적당한 데이터 처리의 경우 비동기 처리를 통해

백엔드 로직을 우선 개선해보고 난 다음에

서버 분리나 RabbitMq 같은 큐 형식의 메세지 브로커를 사용해보면 좋을 것 같다.

 

 

 

 

728x90