본문 바로가기

Reactive-Programming

[Java] 비동기 기술

비동기

  • 현재 작업하던 스레드가 아닌 새로운 스레드를 만들어 해당 스레드에서 작업을 수행하는 것 -> 병렬적으로 테스크를 수행한다.
  • 요청을 보낸 후 결과가 도달하지 않아도 다음 작업을 수행한다.
  • 스레드를 새로 만들고 폐기하는 것은 많은 비용이 소모된다. -> 그만큼 CPU를 더 사용하므로 -> 스레드 풀을 사용해서 스레드를 미리 만들어두고 사용한다. -> 스레드 사용 후에 스레드 풀로 반환

| 코드

// 1. 비동기 확인
ExecutorService es = Executors.newCachedThreadPool();

es.execute(() -> {
    try {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(2000);
        System.out.println(Thread.currentThread().getName() + " Async");
    } catch (InterruptedException e) {}
});
System.out.println(Thread.currentThread().getName() + " Hello!");

System.out.println(Thread.currentThread().getName() + " Exit");

실행 결과

=> 2초라는 시간을 기다리지 않고 Exit가 출력되었다.

Future

  • 자바 1.5부터 시작되었다.
  • 자바의 대표적인 다른 스레드의 결과를 가져올 수 있도록 도와주는 인터페이스
  • 비동기 방식은 서로 다른 스레드를 사용하므로 다른 스레드의 결과를 받으려면 다른 무언가의 도움이 필요하다.
  • Callable의 반환 값이다.
    • Runnable -> 값을 반환하지 않음, Callable -> 값을 반환, 예외를 main thread로 던진다.

| 코드

ExecutorService es = Executors.newCachedThreadPool();


Future<String> submit = es.submit(() -> {
    System.out.println(Thread.currentThread().getName());
    Thread.sleep(2000);
    return Thread.currentThread().getName() + " Async";
});

System.out.println("submit = " + submit.get());

System.out.println(Thread.currentThread().getName() + " Exit");

실행 결과

  • 다른 스레드에서 리턴 값이 올 때까지 대기한 후 출력하고 종료한다. -> Blocking
  • 비동기 코드에서 Blocking 방식을 유용하게 사용할 수 있다.

CallBack

  • 자바에서 비동기 실행 결과 값을 가져오는 방법 중 하나이다.
  • 자바 스크립트 등 다양한 언어에서 해당 방식을 주로 사용한다.
  • 미래의 작업을 매개변수로 넘긴다. -> 결과가 있을 때 실행

| 코드

  • 1번 구현
ExecutorService es = Executors.newCachedThreadPool();

FutureTask<String> futureTask = new FutureTask<>(
    () -> {
        System.out.println(Thread.currentThread().getName());
        Thread.sleep(2000);
        return Thread.currentThread().getName() + " Async";
}) {
    @Override
    protected void done() {
        try {
            System.out.println("futureTask = " + get());
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } catch (ExecutionException e) {
            throw new RuntimeException(e);
        }
    }
};

es.execute(futureTask);

System.out.println(Thread.currentThread().getName() + " Exit");
es.shutdown();
  • 2번 구현
public static void main(String[] args) {

ExecutorService es = Executors.newCachedThreadPool();

CallbackFutureTask callbackFutureTask = new CallbackFutureTask(
        () -> {
            System.out.println(Thread.currentThread().getName());
            Thread.sleep(2000);
            return Thread.currentThread().getName() + " Async";
        },
        System.out::println, // 성공 처리 : Callback
        System.out::println // 에러 처리 : Callback
    );

    es.execute(callbackFutureTask);

    System.out.println(Thread.currentThread().getName() + " Exit");
    es.shutdown();
}

interface SuccessCallback {
    void onSuccess(String result);
}

interface ExceptionCallback {
    void onError(Throwable throwable);
}

public static class CallbackFutureTask extends FutureTask<String> {
    SuccessCallback successCallback;
    ExceptionCallback exceptionCallback;

    public CallbackFutureTask(Callable<String> callable, SuccessCallback successCallback,
                              ExceptionCallback exceptionCallback) {
        super(callable);
        this.successCallback = Objects.requireNonNull(successCallback);
        this.exceptionCallback = Objects.requireNonNull(exceptionCallback);
    }

    @Override
    protected void done() {
        try {
            successCallback.onSuccess(get());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } catch (ExecutionException e) {
            exceptionCallback.onError(e.getCause());
        }
    }
}

실행 결과

=> 다른 작업을 처리하면서, 스레드의 작업이 완료되면 done(callBack 메서드)가 실행된다.

 

* Future.get()은 예외 발생 시 main thread로 던지기 때문에 try-catch 문이 필요하지만 , FutureTask(Callback)를 사용하여 콜백 처리하면 에러 처리 동작을 넘겨 처리할 수 있다. -> 이보다 예외를 처리하는 더 나은 방법이 존재한다.

 

* 위 CallBack의 2번 코드는 스레드 풀 생성, 처리 로직, 성공 처리 로직, 에러 처리 로직, 스레드 실행, 스레드 풀 종료 등 모든 로직이 한 곳에 모여 있다. -> 스프링은 이를 분리하고 추상화하여 사용하기 편리하게 제공한다.

 

* REF

https://www.youtube.com/watch?v=aSTuQiPB4Ns&list=PLOLeoJ50I1kkqC4FuEztT__3xKSfR2fpw&index=4

'Reactive-Programming' 카테고리의 다른 글

[Spring] Reactive Web (2)  (0) 2022.09.27
[Spring] Reactive Web (1)  (0) 2022.09.25
[Reactive Streams] Schedulers  (0) 2022.09.21
[Reactive Streams] Operators  (0) 2022.09.14
[Reactive Streams] Basic  (0) 2022.09.06