Spring에서 제공하는 @Async는 비동기 작업을 간단히 구현할 수 있도록 해줍니다. 이 글에서는 @Async에 대해 포스팅하려고 합니다.
1. @Async란 무엇인가?
@Async는 메서드를 별도의 스레드에서 실행시켜 비동기 처리를 가능하게 하는 Spring 어노테이션입니다. 이를 통해 메인 스레드의 작업 흐름을 차단하지 않고도, 병렬로 작업을 수행할 수 있습니다.
왜 @Async를 사용할까?
- 응답 시간 단축: 백그라운드 작업 처리 중에도 빠르게 응답 반환.
- 리소스 효율화: 스레드 풀을 사용해 리소스를 효과적으로 관리.
- 코드 간소화: 복잡한 스레드 관리 없이 비동기 작업 구현.
2. @Async 설정
@Async를 사용하려면 설정이 필요합니다.
Step 1. @EnableAsync 활성화
Spring 애플리케이션에서 @Async를 활성화하려면 @EnableAsync 어노테이션을 추가합니다.
@Configuration
@EnableAsync
public class AsyncConfig {
}
Step 2. TaskExecutor 커스터마이징
Spring은 기본적으로 SimpleAsyncTaskExecutor를 사용합니다. 하지만 성능 최적화를 위해 커스텀 ThreadPoolTaskExecutor를 설정하는 것이 좋습니다.
@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
@Bean(name = "asyncExecutor")
public Executor asyncTaskExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(10); // 기본 스레드 개수
executor.setMaxPoolSize(30); // 최대 스레드 개수
executor.setQueueCapacity(100); // 대기 작업 수용 용량
executor.setThreadNamePrefix("async-"); // 스레드 이름 접두사
executor.initialize(); // 초기화
return executor;
}
}
TaskExecutor 설정의 의미
- CorePoolSize: 유지할 기본 스레드 수. (10개)
- MaxPoolSize: 최대 생성 가능한 스레드 수. (30개)
- QueueCapacity: 대기 중인 작업의 최대 개수. (100개)
- ThreadNamePrefix: 생성된 스레드 이름의 접두사. (예: async-1, async-2)
3. @Async 기본 사용법
@Async를 메서드에 적용하면, 해당 메서드의 작업이 별도 스레드에서 실행됩니다.
예제: 비동기로 이메일 알림 보내기
1) 서비스 클래스
@Service
public class NotificationService {
@Async("asyncExecutor")
public void sendEmailNotification(String email) {
System.out.println("Sending email to: " + email);
try {
Thread.sleep(5000); // 이메일 전송 시뮬레이션
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Email sent to: " + email);
}
}
2) 컨트롤러 클래스
@RestController
public class NotificationController {
@Autowired
private NotificationService notificationService;
@GetMapping("/send-notification")
public String triggerNotification(@RequestParam String email) {
notificationService.sendEmailNotification(email);
return "Notification triggered for " + email;
}
}
3) 동작 결과
클라이언트는 즉시 응답을 받고, 이메일 전송 작업은 별도의 스레드에서 처리됩니다.
4. 비동기 작업의 반환값 처리
@Async 메서드는 void 외에도 CompletableFuture와 같은 반환 타입을 사용할 수 있습니다.
예제: 비동기로 사용자 데이터 가져오기
1) 서비스 클래스
@Service
public class NotificationService {
@Async("asyncExecutor")
public void sendEmailNotification(String email) {
System.out.println("Sending email to: " + email);
try {
Thread.sleep(5000); // 이메일 전송 시뮬레이션
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
System.out.println("Email sent to: " + email);
}
}
2) 컨트롤러 클래스
@RestController
public class NotificationController {
@Autowired
private NotificationService notificationService;
@GetMapping("/send-notification")
public String triggerNotification(@RequestParam String email) {
notificationService.sendEmailNotification(email);
return "Notification triggered for " + email;
}
}
3) 동작 결과
- 클라이언트는 비동기로 데이터를 받을 수 있습니다.
- 이 방식은 비동기 작업을 호출하는 경우에도 유용합니다.
5. 고급 활용 사례
1) 대용량 데이터 병렬 처리
대량의 데이터를 병렬로 처리하여 작업 시간을 단축할 수 있습니다.
@Service
public class UserService {
@Async("asyncExecutor")
public CompletableFuture<String> fetchUserData() {
try {
Thread.sleep(3000); // 데이터 처리 시뮬레이션
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return CompletableFuture.completedFuture("User Data");
}
}
2) 멀티 TaskExecutor 활용
다양한 TaskExecutor를 정의하고, 특정 작업에 맞는 Executor를 사용할 수 있습니다.
@Async("customExecutor")
public void performTaskWithCustomExecutor() {
System.out.println("Task executed by custom executor");
}
6. 주의사항
1) Self-invocation 문제
동일 클래스 내에서 @Async 메서드를 호출하면 비동기로 동작하지 않습니다.
이를 해결하려면 별도 빈으로 선언된 객체에서 호출해야 합니다.
2) 트랜잭션과의 충돌
@Transactional과 함께 사용할 경우, 비동기 스레드에서 트랜잭션 전파가 제대로 되지 않을 수 있습니다.
이를 고려한 설계가 필요합니다.