비동기
- 현재 작업하던 스레드가 아닌 새로운 스레드를 만들어 해당 스레드에서 작업을 수행하는 것 -> 병렬적으로 테스크를 수행한다.
- 요청을 보낸 후 결과가 도달하지 않아도 다음 작업을 수행한다.
- 스레드를 새로 만들고 폐기하는 것은 많은 비용이 소모된다. -> 그만큼 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로 던진다.
- 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 |