개발/Spring

@Async 어노테이션 사용 - 비동기 메서드 실행

피터JK 2025. 2. 18. 09:55
728x90

Spring에서 @Async를 사용하면 비동기적으로 메서드를 실행할 수 있으며, 이를 통해 별도의 스레드에서 작업을 수행할 수 있습니다.
이를 위해 TaskExecutor와 ThreadPool을 함께 사용하여 효율적인 스레드 관리를 할 수 있습니다.


1. @Async 기본 사용법

@Async는 메서드를 비동기적으로 실행하게 만드는 애너테이션입니다. 이를 사용하려면 @EnableAsync를 설정해야 합니다.

사용 예시

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

@Service
public class AsyncService {

    @Async
    public void asyncMethod() {
        System.out.println("비동기 작업 실행: " + Thread.currentThread().getName());
    }
}

설정 클래스

import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableAsync;

@Configuration
@EnableAsync
public class AsyncConfig {
}

이제 asyncMethod()가 호출되면 별도의 스레드에서 실행됩니다.


2. TaskExecutor와 ThreadPool 설정

Spring에서 @Async가 실행될 때 사용할 스레드풀을 설정하려면 TaskExecutor를 정의해야 합니다.

스레드풀 설정

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;

import java.util.concurrent.Executor;

@Configuration
public class AsyncConfig {

    @Bean(name = "customTaskExecutor")
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);    // 기본적으로 유지할 스레드 수
        executor.setMaxPoolSize(10);    // 최대 스레드 개수
        executor.setQueueCapacity(100); // 대기 큐 크기
        executor.setThreadNamePrefix("AsyncThread-");
        executor.initialize();
        return executor;
    }
}

스레드풀 적용

@Service
public class AsyncService {

    @Async("customTaskExecutor")
    public void asyncMethod() {
        System.out.println("비동기 작업 실행: " + Thread.currentThread().getName());
    }
}
  • "customTaskExecutor"를 명시하면 해당 스레드풀을 사용합니다.
  • 설정하지 않으면 Spring의 기본 SimpleAsyncTaskExecutor가 사용됩니다.

3. 비동기 메서드에서 반환값 처리 (CompletableFuture)

비동기 메서드가 값을 반환해야 한다면 CompletableFuture를 사용할 수 있습니다.

예제

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {

    @Async("customTaskExecutor")
    public CompletableFuture<String> asyncMethodWithReturn() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
        return CompletableFuture.completedFuture("비동기 작업 완료!");
    }
}

호출 코드

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.util.concurrent.CompletableFuture;

@Component
public class AsyncCaller {

    @Autowired
    private AsyncService asyncService;

    public void callAsyncMethod() {
        CompletableFuture<String> future = asyncService.asyncMethodWithReturn();
        future.thenAccept(result -> System.out.println("결과: " + result));
    }
}

4. @Async 동작 방식 및 주의점

  1. 프록시 기반 동작
    • @Async는 AOP 기반 프록시를 사용하므로 같은 클래스 내의 메서드에서는 @Async가 적용되지 않습니다.
    • 해결책: 같은 클래스 내에서 사용하려면 별도의 빈으로 분리해야 합니다.
  2. 비동기 메서드는 public이어야 함
    • 프록시를 통해 호출되므로 private 메서드에는 적용되지 않습니다.
  3. 트랜잭션(@Transactional)과 함께 사용할 때 주의
    • @Async가 적용된 메서드는 별도 스레드에서 실행되므로, @Transactional이 적용된 경우 트랜잭션이 분리될 수 있습니다.
    • 해결책: 트랜잭션을 유지하려면 같은 스레드 내에서 실행하도록 조정해야 합니다.

5. 정리

  • @Async를 사용하면 메서드를 비동기적으로 실행할 수 있음.
  • @EnableAsync를 설정해야 함.
  • TaskExecutor를 설정하면 효율적인 스레드풀 관리 가능.
  • 반환값이 필요하면 CompletableFuture 사용.
  • 프록시 방식이므로 같은 클래스 내에서는 적용되지 않음.

이제 @Async, TaskExecutor, 그리고 ThreadPool을 활용하여 비동기 처리를 더욱 효과적으로 할 수 있습니다. 

728x90