개발은 재밌어야 한다
article thumbnail

Spring에서 제공하는 @Async는 비동기 작업을 간단히 구현할 수 있도록 해줍니다. 이 글에서는 @Async에 대해 포스팅하려고 합니다.


1. @Async란 무엇인가?

@Async는 메서드를 별도의 스레드에서 실행시켜 비동기 처리를 가능하게 하는 Spring 어노테이션입니다. 이를 통해 메인 스레드의 작업 흐름을 차단하지 않고도, 병렬로 작업을 수행할 수 있습니다.

왜 @Async를 사용할까?

  1. 응답 시간 단축: 백그라운드 작업 처리 중에도 빠르게 응답 반환.
  2. 리소스 효율화: 스레드 풀을 사용해 리소스를 효과적으로 관리.
  3. 코드 간소화: 복잡한 스레드 관리 없이 비동기 작업 구현.

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 설정의 의미

  1. CorePoolSize: 유지할 기본 스레드 수. (10개)
  2. MaxPoolSize: 최대 생성 가능한 스레드 수. (30개)
  3. QueueCapacity: 대기 중인 작업의 최대 개수. (100개)
  4. 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과 함께 사용할 경우, 비동기 스레드에서 트랜잭션 전파가 제대로 되지 않을 수 있습니다.
이를 고려한 설계가 필요합니다.

profile

개발은 재밌어야 한다

@ghyeong

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!