자바의 비동기 처리 기술

wwlee94·2022년 12월 31일
0

비동기 처리

목록 보기
1/2
post-thumbnail

자바의 비동기 처리 기술 알아보기

자바의 비동기 처리 관련 라이브러리의 여러 종류에 대해서 알아보고 직접 실행 시켜보면서 동작을 확인해보자.

ExecutorService

JDK 1.5 부터 생성된 비동기 작업 실행을 도와주는 Interface

ExecutorService는 생성과 동시에 작업이 수행된다.

@Slf4j
public class FutureExample {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();

        es.execute(() -> {
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) { }
            log.info("Async");
        });

        log.info("Exit");

        es.shutdown();
    }
}

실행 결과

  • Exit 호출 이후에 2초 후 Async 호출이 된다.
19:48:48.487 [main] INFO com.java.playground.future.FutureExample - Exit
19:48:50.490 [pool-1-thread-1] INFO com.java.playground.future.FutureExample - Async

스레드 풀이란?

기본적으로 스레드를 새로 만들고 삭제하는 작업은 비용이 많이 든다.

비용이 든다는 이야기는 컴퓨팅 인스턴스의 CPU 나 메모리를 소모한다는 뜻

그러한 비용을 줄이기 위해서 아래와 같은 방법을 이용한다.

스레드 풀을 만들어두고 작업 요청이 들어오면 새 스레드를 생성한 뒤 사용하고 반납하면서 캐싱하면서 비용을 줄인다.

ExecutorService es = Executors.newCachedThreadPool(); // 1번 방식
ExecutorService es = Executors.newFixedThreadPool(5); // 2번 방식

newCachedThreadPool 과 newFixedThreadPool 의 차이점

newCachedThreadPool 의 경우 1개 이상의 스레드가 추가되었을 경우 60초 동안 스레드가 아무 작업 하지 않으면 스레드를 종료하고 풀에서 제거한다.

newFixedThreadPool 의 경우 스레드가 작업을 처리하지 않고 놀고 있더라도 적어도 n개의 스레드 이하로 줄지 않는다.

Future

JDK 1.5에 등장한 비동기 계산의 결과를 받을 수 있는 Interface 이다.

비동기 수행한다는 의미는 별도 스레드에서 작업이 되는데, 별도 스레드에서 완료된 결과를 받을 수 있는 interface가 바로 Future이다.

@Slf4j
public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        Future<String> future = es.submit(() -> {
            Thread.sleep(2000);
            log.info("Async");
            return "Hello";
        });

        log.info("Exit");
        log.info(future.get()); // Blocking

        es.shutdown();
    }
}

실행 결과

19:59:08.430 [main] INFO com.java.playground.future.FutureExample - Exit
19:59:10.434 [pool-1-thread-1] INFO com.java.playground.future.FutureExample - Async
19:59:10.434 [main] INFO com.java.playground.future.FutureExample - Hello

get() 메서드로 결과를 전달 받거나 isDone() 메서드로 작업이 완료되었는지 여부를 확인 가능

  • 단, get()으로 결과를 받기 위해서는 별도 스레드가 block되기 때문에 주의!

FutureTask

Future의 경우 작업의 생성과 실행이 동시에 이루어지지만 FutureTask의 경우 작업의 생성과 실행을 분리 가능하다.

FutureTask도 JDK 1.5에서 추가된 클래스이다.

@Slf4j
public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        FutureTask<String> futureTask = new FutureTask<>(() -> {
            Thread.sleep(2000);
            log.info("Async");
            return "Hello";
        }); // Create

        es.execute(futureTask);// Run
        
        log.info("Exit");
        log.info(futureTask.get()); // Blocking

        es.shutdown();
    }
}

실행 결과

20:14:41.464 [main] INFO com.java.playground.future.FutureExample - Exit
20:14:43.466 [pool-1-thread-1] INFO com.java.playground.future.FutureExample - Async
20:14:43.468 [main] INFO com.java.playground.future.FutureExample - Hello

CompletableFuture

CompletableFuture 는 JDK 1.8 버전부터 추가되었으며 Future와 다르게 외부에서 완료 시킬 수 있고 콜백 등록, Future 조합 사용이 가능한 클래스이다.

첫번째 예제

1번 작업은 1초간 대기하고 20을 반환하는 f1() 함수이고, 2번작업은 2초간 대기하고 30을 반환하는 f2() 함수

public static int f1(){
   Thread.sleep(1000);
   return 20;
}

public static int f2(){
   Thread.sleep(2000);
   return 30;
}
@Slf4j
public class CompletableFutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        CompletableFuture<Integer> a = new CompletableFuture<>();
        CompletableFuture<Integer> b = new CompletableFuture<>();
        es.submit(() -> a.complete(f1()));
        es.submit(() -> b.complete(f2()));

        log.info("Sum : {}", a.get() + b.get());

        log.info("Exit");

        es.shutdown();
    }
}

실행 결과

21:04:22.098 [main] INFO com.java.playground.future.CompletableFutureExample - Start
21:04:24.109 [main] INFO com.java.playground.future.CompletableFutureExample - Sum : 50
21:04:24.112 [main] INFO com.java.playground.future.CompletableFutureExample - Exit

작업이 시작하고 Sum의 결과를 전달 받을때 까지 2초 걸린 것을 확인 할 수 있으며, 메인 스레드에서 동작하는 Exit 을 수행하지 못하고 Block된 것을 확인 할 수 있다.

두번째 예제

@Slf4j
public class CompletableFutureExample {
public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        log.info("Start");

        CompletableFuture<Integer> a = new CompletableFuture<>();
        CompletableFuture<Integer> b = new CompletableFuture<>();
        CompletableFuture<Integer> c = a.thenCombine(b, (x, y) -> x + y);
        es.submit(() -> a.complete(f1()));
        es.submit(() -> b.complete(f2()));
        es.submit(() -> {
            try {
                log.info("Sum : {}", c.get());
            } catch (InterruptedException e | ExecutionException e) {}
        });

        log.info("Exit");

        es.shutdown();
    }
}

실행 결과

21:09:41.070 [main] INFO com.java.playground.future.CompletableFutureExample - Start
21:09:41.083 [main] INFO com.java.playground.future.CompletableFutureExample - Exit
21:09:42.086 [pool-1-thread-1] INFO com.java.playground.future.CompletableFutureExample - f1 logging !
21:09:43.087 [pool-1-thread-2] INFO com.java.playground.future.CompletableFutureExample - f2 logging !
21:09:43.087 [pool-1-thread-3] INFO com.java.playground.future.CompletableFutureExample - Sum : 50

합계를 계산하는 작업 또한 태스크로 만들어서 사용하면 메인스레드를 바로 종료시키고 별도 스레드 내에서 전부 처리 할 수 있다.

팩토리 메서드 활용

CompletableFuture는 아래의 팩토리 메서드를 제공한다.

비동기 작업 실행

  • runAsync
    • 반환 값이 없는 경우
  • supplyAsync
    • 반환 값이 있는 경우

작업 실행은 자바7에 추가된 ForkJoinPool의 commonPool()을 사용해 작업을 실행할 쓰레드를 쓰레드 풀로부터 얻어 실행시킨다.

만약 별도의 생성된 쓰레드 풀을 사용하려면, ExecutorService를 파라미터로 넘겨주면 된다.

두번째 예제의 코드를 팩토리 메서드로 바꾸면 아래와 같이 바꿀 수 있다. 결과는 동일하다.

@Slf4j
public class CompletableFutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        log.info("Start");

        CompletableFuture<Integer> a = CompletableFuture.supplyAsync(() -> f1(), es);
        CompletableFuture<Integer> b = CompletableFuture.supplyAsync(() -> f2(), es);
        CompletableFuture<Void> c = CompletableFuture.runAsync(() -> {
            CompletableFuture<Integer> future = a.thenCombine(b, (x, y) -> x + y);
            try {
                log.info("Sum : {}", future.get());
            } catch (InterruptedException | ExecutionException e) { }
        }, es);

        log.info("Exit");

        es.shutdown();
    }
}

예외 처리

  • exeptionally
    • 발생한 에러를 받아서 외부에서 예외 처리 가능
  • handle, handleAsync
    • 결과값과 에러를 받아서 에러가 발생하지 않은 경우 (결과값), 에러가 발생한 경우 (에러) 를 받아서 처리할 수 있다.
@Slf4j
public class CompletableFutureExceptionExample {
    public static String f1(){
        if (1==1) throw new RuntimeException("Async Exception !");
        return "Success !";
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();

        log.info("Start");

        CompletableFuture<String> a = CompletableFuture.supplyAsync(() -> f1(), es)
                .exceptionally(Throwable::getMessage);

        log.info(a.get());

        log.info("Exit");

        es.shutdown();
    }
}

실행 결과

21:34:07.465 [main] INFO com.java.playground.future.CompletableFutureExceptionExample - Start
21:34:07.474 [main] INFO com.java.playground.future.CompletableFutureExceptionExample - java.lang.RuntimeException: Async Exception !
21:34:07.474 [main] INFO com.java.playground.future.CompletableFutureExceptionExample - Exit

마무리

Java의 비동기 처리 라이브러리에 대해서 기본적인 개념과 사용법에 대하여 알아보았다.

스레드 풀 관리부터 비동기 계산 결과를 받을 수 있는 Future와 외부에서 결과값과 예외를 받을 수 있고 Future간 연산도 가능하게 해주는 CompletableFuture 부터 다양하게 알 수 있었다.

다음 포스팅에서는 스프링에서 제공해주는 비동기 기술에 대해서 알아보자.

레퍼런스

https://www.youtube.com/watch?v=aSTuQiPB4Ns

http://www.yes24.com/Product/Goods/77125987

profile
우엉이의 개발 블로그 📝

0개의 댓글