====== Spring @Async ======
* [[http://www.baeldung.com/spring-async|Spring Async]]
* [[https://docs.spring.io/spring/docs/4.1.x/javadoc-api/org/springframework/scheduling/concurrent/ThreadPoolTaskExecutor.html|ThreadPoolTaskExecutor]]로 비동기 코드 실행
* [[java:concurrent:executorservice|Java ExecutorService]]
* 비동기 작업시 [[springframework:bean_lifecycle|Spring Framework Bean Lifecycle]]에 있는 ''SmartLifeCycle''을 구현해줘야만 한다.
===== 설정 =====
* Java 8 이후 버전에서는 [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/scheduling/annotation/AsyncConfigurer.html|AsyncConfigurer]] interface를 구현하면 된다. default method 라서 원하는 것만 선택구현가능함.
@EnableAsync
public class SpringAsyncConfig extends AsyncConfigurerSupport {
// 기본 taskExecutor
@Bean // @Bean 필수
@Override
public Executor getAsyncExecutor() {
return new ThreadPoolTaskExecutor(); // 객체 설정해줄것.
}
}
===== ThreadPoolTaskExecutor 설정 =====
* [[java:concurrent:executorservice|Java ExecutorService]] 참조 Spring 의 ThreadPool
* wait 설정으로 서버 종료시 남은 작업을 기다릴 시간 확보해야함. 아래를 안해주면 서버가 그냥 종료 돼 버린다.
* ''waitForTasksToCompleteOnShutdown=true''
* ''awaitTerminationSeconds=초''
* ''beanName''을 지정해주면 로그에 이름이 찍힘.
* [[https://github.com/kwon37xi/java-spring-thread-pool-test|kwon37xi/java-spring-thread-pool-test: Java와 SpringFramework 의 Thread Pool 작동 방식 테스트]]
* fixed? cached?
* 서버 종료시 올바로 종료됨을 보장하려면 [[springframework:bean_lifecycle|Spring Framework Bean Lifecycle]] [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/context/SmartLifecycle.html|SmartLifeCycle]]도 구현해야한다.
===== Cached Thread Pool 효과 =====
* corePoolSize 는 적게 혹은 0, maxPoolSize=''Interger.MAX_VALUE'', queueCapacity를 0으로 만들면 Cached Thread Pool 처럼 작동한다.
* 기본 CorePoolSize 만큼 풀을 생성하고,
* 필요하면 쓰레드풀을 최대 maxPoolSize 만큼 증가시키고
* 안 사용하는 시간이 KeepAliveSeconds가 지나면 쓰레드를 없앤다.
* cachedThreadPool 은 느려지는 태스크 실행시 쓰레드 갯수가 폭증해서 시스템을 다운시킬 수도 있다.
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("XXXX-");
threadPoolTaskExecutor.setCorePoolSize(0);
threadPoolTaskExecutor.setMaxPoolSize(Integer.MAX_VALUE);
threadPoolTaskExecutor.setQueueCapacity(0);
threadPoolTaskExecutor.setKeepAliveSeconds(60);
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setAwaitTerminationSeconds(15);
===== MDC 사용하기 =====
* ''@Async''로 새로 생성된 쓰레드에서 호출자 쓰레드의 [[java:slf4j:mdc|Slf4j MDC]]를 복제하도록 할 수 있다.
* [[https://moelholm.com/2017/07/24/spring-4-3-using-a-taskdecorator-to-copy-mdc-data-to-async-threads/|Spring 4.3: Using a TaskDecorator to copy MDC data to @Async threads]]
==== ThreadPoolTaskExecutor 설정에 Decorator 지정 ====
@Override
public Executor getAsyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setTaskDecorator(new MdcTaskDecorator());
executor.initialize();
return executor;
}
==== MdcTaskDecorator.java ====
class MdcTaskDecorator implements TaskDecorator {
@Override
public Runnable decorate(Runnable runnable) {
// Right now: Web thread context !
// (Grab the current thread MDC data)
Map contextMap = MDC.getCopyOfContextMap();
return () -> {
try {
// Right now: @Async thread context !
// (Restore the Web thread context's MDC data)
if (contextMap != null) {
MDC.setContextMap(contextMap);
}
runnable.run();
} finally {
MDC.clear();
}
};
}
}
===== CompletableFuture 지원 =====
* [[java:8:completable_future|Java 8 CompletableFuture]]
* [[https://www.javacodegeeks.com/2016/04/spring-async-javas-8-completablefuture.html|Spring Async and Java's 8 CompletableFuture]]
* [[https://spring.io/guides/gs/async-method/|Getting Started | Creating Asynchronous Methods]]
* [[https://dzone.com/articles/spring-boot-async-methods|Spring Boot - Async methods - DZone Java]]
* 아래와 같이 ''CompletableFuture.completedFuture()''를 리턴하게 만들면 Spring 이 내부적으로 [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/aop/interceptor/AsyncExecutionInterceptor.html|AsyncExecutionInterceptor]]에서 진짜 ''CompletableFuture.supplyAsync''로 변환해서 리턴한다. [[https://github.com/spring-projects/spring-framework/blob/master/spring-aop/src/main/java/org/springframework/aop/interceptor/AsyncExecutionAspectSupport.java|AsyncExecutionAspectSupport.java 소스]] [[https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/util/concurrent/ListenableFuture.html|ListenableFuture]]와 [[https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Future.html|Future]]도 마찬가지로 작동함.
@Async
public CompletableFuture findUser(String user) throws InterruptedException {
logger.info("Looking up " + user);
String url = String.format("https://api.github.com/users/%s", user);
User results = restTemplate.getForObject(url, User.class);
Thread.sleep(1000L);
return CompletableFuture.completedFuture(results); //IT IS completedFuture
}