Spring
[Java] 비동기 기초 Future, Callback
별토끼.
2021. 2. 10. 17:59
반응형
JAVA의 비동기 기술
본 정리는 토비의봄 8회 리액티브 프로그래밍(4) 자바와 스프링의 비동기 기술을 토대로 정리한 내용입니다.
(무려 10년 전 사용하던 기술....현재는 더 좋은 방법이 존재하나 토대를 튼튼히 하는 것이 중요하므로..)
동기와 비동기
동기코드는 어떻게 작성했나?
- 2초 sleep 후 Hello 출력
- 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() 걸어줘야 함.
- exit 우선 출력
- 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(); //이 메서드를 쓰더라도 하던 작업이 중단되지는 않음
}
}
반응형