만약 자바스크립트를 배워본적이 있다면 비동기 논블로킹이라는 개념을 잘 알고있을것이다
그리고 자바스크립트의 핵심기능인 Async도 잘 알고있을 것이다
난 이게 자바에는 없는줄 알았는데 Spring 기능중에 Async라는 기능이 존재했었고
그 기능에 대해 살짝 알아본걸 여기에 적어본다
spring async란 thread pool을 이용해 비동기 처리를 진행하는 어노테이션이다
기존의 자바를 이용해서도 처리가 가능했지만 async를 이용하면 간편하게 처리가 가능하다
기본적으로 비동기로 사용하고 싶은 메서드가 있는 클래스에 @EnableAsync
를 붙이고
메서드에 @Async
를 붙인다
@EnableAsync
public class Service {
@Async
public int async() {
}
}
이렇게 설정하면 스프링부트에서 간단히 비동기 논 블로킹 처리가 가능하다
간단히 테스트를 해보면
public void asyncTest() {
log.info("메서드 시작");
async.async();
log.info("메서드 끝");
}
@Async
public void async() {
log.info("비동기 작업 처리 시작");
for (int i=0; i<100000; i++) {}
log.info("비동기 작업 처리 완료");
}
보통 자바라면 하나씩 실행하기때문에 for에 있는 i를 10만번 더하는 작업이 완료된 후 메서드 끝이 출력되어야한다
하지만 결과는 다르게 나온다
비동기 논 블로킹 처리가 이루어져서 메서드가 시작하자마자 끝으로 넘어가고
다른 스레드에서 비동기 작업을 처리한다
이렇게 간단하게 비동기 논블로킹을 구현했다
하지만 각 서버마다 cpu와 ram의 성능이 다르고 사람마다 스레드 풀을 설정하고싶은게 다르다
하지만 이렇게 기본적으로 @EnableAsync
를 써서 구현하면 스레드 풀을 스프링에서 설정한 기본값으로 작동하게 된다 하지만 기본값은 최적화된 설정이 아니기때문에 작성자가 직접 설정을 해줄수 있다
ThreadPoolTaskExecutor
를 이용해 직접 @Async
에 대한 스레드 풀 설정을 해줄 수 있다
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
executor.setMaxPoolSize(50);
executor.setQueueCapacity(100);
executor.setKeepAliveSeconds(120);
executor.setAllowCoreThreadTimeOut(true);
executor.setPrestartAllCoreThreads(true);
executor.setWaitForTasksToCompleteOnShutdown(true);
executor.setAwaitTerminationSeconds(20);
executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
executor.setThreadNamePrefix("Async-Test");
executor.initialize();
return executor;
}
}
설정이 아주 다양하고 자세하다. 하나씩 살펴보자
설정들을 잘 활용하면 최적의 스레드를 사용해 비동기 처리를 사용할 수 있다
여기서 만약 두개의 @Async
메서드를 생성하고 둘 다 설정을 다르게 해주고 싶을때는 어떻게 할까?
방법은 bean에 해당 Async 메서드의 이름을 달면 된다
@Configuration
@EnableAsync
public class AsyncConfig {
@Bean(name = "testAsync")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(5);
return executor;
}
}
메서드를 실행하고 리턴값이 없다면 괜찮지만 만약 리턴값이 있다면 어떻게 해야할까
바로 콜백을 이용하면된다
일반적인 리턴을 할 경우 비동기 논블로킹으로 동작하지않기 때문에 Future
라는 객체를 이용해 콜백을 시켜준다
Future
에는 Future
,ListenableFutue
,CompletableFuture
등이 존재하는데
스프링부트3에서는 CompletableFuture
가 동작하므로 나는 이 객체를 사용했다
@Async
public CompletableFuture<String> async() {
log.info("비동기 작업 처리 시작");
int i;
for (i=0; i<100000; i++) {}
log.info("비동기 작업 처리 완료");
String futureStr = i + "번 for문 반복 완료";
return CompletableFuture.completedFuture(futureStr);
}
Async메서드에 CompletableFutre라는 객체를 리턴받게하고 그 안에 futureStr이라는 String을 넣었다
public String asyncTest() {
log.info("메서드 시작");
CompletableFuture<String> future = async.async();
future.thenAccept(str -> {
log.info("리턴값 콜백 완료: " + str);
});
log.info("메서드 끝");
return "index";
}
그리고 실행할 메서드에서 값을 CompletableFuture로 받고 thenAccept를 이용해 정상적으로 받아진 값을 람다식을 이용해 처리하였다
일반적으로 Async를 이용하지않은 코드일경우에는 log가 밑에처럼 뜰것이다
1. 메서드 시작
2. 비동기 작업 처리 시작
3. 비동기 작업 처리 끝
4. 리턴값 콜백 완료: 1000000번 for문 반복 완료
5. 메서드 끝
하지만 CompletableFuture
를 이용해 콜백 형식으로 받으면 다음과 같은 결과가 나온다
이렇게 콜백 패턴이 정상적으로 적용된 것을 볼 수 있다.
@Transactional
설정과 함께 사용할경우 동작이 잘 안된다Async
가 적용된 메서드일경우 해당 메서드를 가로채 다른 스레드에서 작업을 시키기 때문이다