@Async

김개발세발바닥·2024년 8월 15일

스프링

목록 보기
13/13

@Async

스프링에서 비동기 프로그래밍을 구현하는 방식 중 하나다.

Exception Handling.

@Async 어노테이션이 붙은 메서드가 별도의 스레드에서 실행되므로 메인 스레드에서 캐치를 할 수 없다.
예외를 처리하기 위해서는 AsyncUncaughtExceptionHandler를 사용하여 예외를 적절히 처리해야 한다.

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {
 
    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncUncaughtExceptionHandler();
    }
}

public class MyAsyncUncaughtExceptionHandler implements AsyncUncaughtExceptionHandler {
    @Override
    public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
        System.out.println("Exception message - " + throwable.getMessage());
        System.out.println("Method name - " + method.getName());
        for (Object param : obj) {
            System.out.println("Parameter value - " + param);
        }
    }
}

호출.

@Async 어노테이션은 프록시 기반으로 동작하기 때문에 같은 클래스 내부에서 직접 호출하면 별도에 스레드에서 실행되지 않고 동기적으로 실행된다.
@Transactional이랑 같은 현상.
이를 해결하려면 self reference를 사용해야 하거나 비동기 처리하는 로직을 별도의 클래스로 빼서 호출해야 한다.

@Service
public class SelfInvocationService {
    @Autowired
    private SelfInvocationService self;
 
    public void method() {
        self.asyncMethod();
    }
    
    @Async
    public void asyncMethod() {
    }
}

리턴 타입.

@Async가 붙은 메서드는 void, Future, CompletableFuture중 하나를 반환해야 한다.
Future를 반환하면 비동기 작업 결과를 반환하고 작업 완료를 대기할 수 있다.
하지만 Future나 CompletableFuture를 반환 받아도 완료 값이 없을 수 있다.

@Service
public class xxxService {
    @Autowired
    private AsyncService asyncService;
 
    public void method() {
        Future<String> future = asyncService.asyncMethodWithReturnType();
        String result = future.get();
        System.out.println(result);
    }
}

이럴 땐 future.get()을 호출해서 비동기처리한 결과값이 담길 때 까지 기다릴 수 있다.

트랜잭션.

@Async 메서드는 호출한 메서드와 독립적인 스레드에서 동작한다.
따라서 비동기 메서드 내에서 생성된 트랜잭션은 호출한 메서드의 트랜잭션과 별도의 생명주기를 가진다.
따라서 비동기 메서드 내에 예외가 발생해서 트랜잭션이 롤백 되어야 할 때에도 독립적인 스레드 트랜잭션 에서만 처리되고 원래 호출 한 스레드의 트랜잭션에는 영향이 미치지 않는다.
그냥 따로 트랜잭션이 생성된다고 보는게 쉬울 것 같다.

Executor.

스프링에서 비동기 작업을 하고 싶다면 @EnableAsync, @Async를 사용해야한다.
하지만 기본으로 설정하면 SimpleAsyncTaskExecutor가 사용된다.
SimpleAsyncTaskExecutor는 비동기 작업마다 새로운 스레드를 생성하는 스레드풀이다. 이로인해 리소스 낭비, 성능 저하, 스케일링 문제 등이 발생할 수 있다.

@Configuration
@EnableAsync
public class AsyncConfig {
 
    @Bean(name = "threadPoolTaskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(2);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(500);
        executor.setThreadNamePrefix("Executor-");
        executor.initialize();
        return executor;
    }
}

이런식으로 빈을 등록하면 미리 만들어진 스레드 풀에서 스레드를 사용한다.
스레드를 안만들고 재사용하니 성능에도 좋고 정해진 트래픽을 받기 때문에 리소스 낭비도 없다.

0개의 댓글