본문 바로가기
Spring

[Java] 비동기 기초 Future, Callback

by 별토끼. 2021. 2. 10.
반응형

JAVA의 비동기 기술

본 정리는 토비의봄 8회 리액티브 프로그래밍(4) 자바와 스프링의 비동기 기술을 토대로 정리한 내용입니다.

(무려 10년 전 사용하던 기술....현재는 더 좋은 방법이 존재하나 토대를 튼튼히 하는 것이 중요하므로..)

동기와 비동기

동기코드는 어떻게 작성했나?

  1. 2초 sleep 후 Hello 출력
  2. Exit출력
public class FutureEx {
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        Thread.sleep(2000);
        log.info("Hello");
        log.info("Exit");
    }
}

log결과

[main] INFO com.example.demo.FutureEx - Hello
[main] INFO com.example.demo.FutureEx - Exit

비동기의 기초

execute

특징

  • runnable 인터페이스 필요, result값 설정 가능
  • runnable - 객체를 리턴하지 않음, exception 발생시키지 않음
  • 고로, 객체를 리턴할 필요가 없을 때 사용한다.
  • shutdown() 걸어줘야 함.
  1. exit 우선 출력
  2. 2초 후 Hello 출력
public class FutureEx {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        es.execute(() -> {
            try {
                Thread.sleep(2000); //interrupt 발생시 exception 던질 수 있도록
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.info("Hello");
        });
        log.info("Exit");
    }
}

log결과

[main] INFO com.example.demo.FutureEx - Exit
[pool-1-thread-1] INFO com.example.demo.FutureEx - Hello

submit

특징

  • runnable, callable 인터페이스 사용 가능
  • runnable - 객체를 리턴하지 않음, exception 발생시키지 않음
  • callable - 값 리턴 가능, exception 발생시킬 수 있음
  • 고로, 객체 리턴이 필요하거나 exception 발생이 필요할 때 사용한다.
public class FutureEx {
    public static void main(String[] args) {
        ExecutorService es = Executors.newCachedThreadPool();
        es.submit(() -> {
            Thread.sleep(2000); //interrupt 발생시 exception 던질 수 있도록
            log.info("Async");
            return "Hello";
        });
        log.info("Exit");
    }
}

Java Future와 FutureTask, Callback

submit으로 리턴 받은 비동기 수행 결과값을 저장할 때 Future와 Callback을 사용한다.

future

특징

  • Future는 자바1.5에서 나왔다.
  • 비동기적 연산, 작업을 수행한 후 도출된 결과를 나타내는 것
  • 타 thread에서 return한 값을 메인Thread에서 받고 싶을 때 사용
public class FutureEx {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        Future<String> f = es.submit(() -> {
            Thread.sleep(2000); //interrupt 발생시 exception 던질 수 있도록
            log.info("Async");
            return "Hello";
        });
        System.out.println(f.isDone()); //즉시 리턴(작업이 완료되었는지)
        Thread.sleep(2100);
        log.info("Exit");
        System.out.println(f.isDone());
        System.out.println(f.get());
    }
}

log결과

false
[pool-1-thread-1] INFO com.example.demo.FutureEx - Async
[main] INFO com.example.demo.FutureEx - Exit
true
Hello

.

왜 log결과 Exit가 맨 끝에 있을까?

System.out.println(f.get())에서 get()메서드는 결과값을 받을 때까지 대기하는 Blocking 메서드이기 때문이다.

그럼, 굳이 ThreadPool만들어서 이렇게 구현할까? 이것 나름으로 Observer패턴 등을 활용할 때 유용하게 사용된다고 한다.

isDone()

작업이 완료되었는지 확인하는 메서드. 결과 값이 즉시 리턴됨

FutureTask

Future 자체를 Object로 만들어줌.

FutureTask를 이용한 코드

public class FutureEx {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        FutureTask<String> f = new FutureTask<>(()->{
            Thread.sleep(2000);
            log.info("Async");
            return "Hello";
        });
        es.execute(f);

        System.out.println(f.isDone()); //즉시 리턴(작업이 완료되었는지)
        Thread.sleep(2100);
        log.info("Exit");
        System.out.println(f.isDone());
        System.out.println(f.get());
    }
}

FutureTask에 익명클래스로 done()메서드를 추가한 코드

  • 2초 후 Async 출력
  • Hello가 리턴되며 done()이 호출됨
  • get()호출로 Hello가 출력
public class FutureEx {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        ExecutorService es = Executors.newCachedThreadPool();
        FutureTask<String> f = new FutureTask<>(()->{
            Thread.sleep(2000);
            log.info("Async");
            return "Hello";
        }) { //비동기 작업이 모두 완료되면 호출되는 hook같은 것.
            @Override
            protected void done() {
                try {
                    System.out.println(get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
            }
        };
        es.execute(f);
        es.shutdown(); //이 메서드를 쓰더라도 하던 작업이 중단되지는 않음
    }
}

Callback

  • Callback을 이용하여 비동기 실행 결과를 처리할 수 있는 코드
  • try/catch 작성이 빈번한 Future보다 더 우아한 코드라고 할 수 있다..!
  • 더 나은 방법이 있지만, 기본기 중에서는 콜백 기법이 더 나음
public class FutureEx {
    interface SuccessCallback {
        void onSuccess(String result);
    }
    interface  ExceptionalCallback{
        void onError(Throwable t);
    }
    public static class CallbackFutureTask extends FutureTask<String> {
        SuccessCallback sc;
        ExceptionalCallback ec;
        public CallbackFutureTask(Callable<String> callable, SuccessCallback sc, ExceptionalCallback ec) {
            super(callable);
            this.sc = Objects.requireNonNull(sc); //Tip. Null이 들어오면 안될 때 사용하는 메서드
            this.ec = Objects.requireNonNull(ec);
        }

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

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

        CallbackFutureTask f = new CallbackFutureTask(() -> {
            Thread.sleep(2000);
            if(1==1) throw new RuntimeException("Async ERROR!!!");
            log.info("Async");
            return "Hello";
        },
            s -> System.out.println(s),
            e-> System.out.println("Error: "+e.getMessage()));

        es.execute(f);
        es.shutdown(); //이 메서드를 쓰더라도 하던 작업이 중단되지는 않음
    }
}
반응형

댓글