본문 바로가기
Spring

비동기 프로그래밍 @Async와 CompletableFuture

by 흑시바 2024. 8. 29.

비동기 프로그래밍의 두 가지 접근법

비동기 프로그래밍은 현대 애플리케이션에서 성능을 최적화하고 사용자 경험을 향상하기 위해 필수적인 기술이다. Java에서는 비동기 프로그래밍을 지원하는 여러 도구가 있으며, 그중에서도 Spring Framework의 @Async 어노테이션과 Java 표준 라이브러리의 CompletableFuture가 많이 사용된다.

 

그러면 어떤 경우에 @Async를 사용하고 CompletableFuture를 사용해야 할까?

 

해당 포스트를 통해 @Async와 CompletableFuture의 차이점과 각각의 사용 사례를 구체적으로 살펴보자.

 


@Async - Spring의 간편한 비동기 처리

@Async는 Spring Framework에서 제공하는 어노테이션으로, 메서드를 비동기적으로 실행할 수 있게 해 준다. 주로 Spring 애플리케이션에서 간단하게 비동기 작업을 수행할 때 사용되며, 다음과 같은 특징을 가지고 있다.

 

- @Async 어노테이션을 추가하면 해당 메서드는 별도의 스레드에서 비동기적으로 실행된다.

- void, Future, CompletableFuture 등을 리턴 타입으로 사용할 수 있다.

- 기본적으로 Spring의 TaskExecutor를 사용하여 스레드를 관리한다. 필요에 따라 커스터마이징 할 수 있다.

- 비동기 메서드에서 발생한 예외는 호출자에게 직접 전달되지 않으며, Future나 CompletableFuture를 사용하여 예외를 처리할 수 있다.

사용 예제

Spring Framework에서 @Async 어노테이션을 사용하여 비동기 메서드를 실행하려면 몇 가지 설정이 필요하다. 아래에서는 @Async를 사용하기 위해 필요한 설정과 단계별로 어떻게 설정하는지에 대해 설명한다.

1. @EnableAsync 어노테이션 추가

@Async 어노테이션을 사용하려면, Spring 애플리케이션에서 비동기 처리를 활성화해야 한다. 이를 위해 @EnableAsync 어노테이션을 사용한다. 이 어노테이션은 주로 애플리케이션의 메인 클래스나 설정 클래스에 추가된다.

 

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableAsync;

@SpringBootApplication
@EnableAsync
public class ShibaHolicApplication {

    public static void main(String[] args) {
        SpringApplication.run(ShibaHolicApplication.class, args);
    }
}

2. 비동기 메서드 정의

@Async 어노테이션을 사용하여 비동기 메서드를 정의할 수 있다. 비동기 메서드는 @Async 어노테이션을 추가하고, void, Future, CompletableFuture 등의 리턴 타입을 가질 수 있다.

 

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {

    @Async
    public void asyncMethod() {
        // 비동기적으로 실행될 코드
        System.out.println("Async method execution");
    }

    @Async
    public CompletableFuture<String> asyncMethodWithReturn() {
        // 비동기적으로 실행될 코드
        return CompletableFuture.completedFuture("Hello, Shiba!");
    }
}

3. TaskExecutor 커스터마이징 (선택 사항)

기본적으로 Spring은 SimpleAsyncTaskExecutor를 사용하여 비동기 작업을 처리한다. 하지만 필요에 따라 커스텀 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
    public Executor taskExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(25);
        executor.setThreadNamePrefix("ShibaHolic-");
        executor.initialize();
        return executor;
    }
}

4. 예외 처리

비동기 메서드에서 발생한 예외는 호출자에게 직접 전달되지 않는다. 예외를 처리하려면 Future나 CompletableFuture를 사용하여 예외를 처리할 수 있다.

 

import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;

@Service
public class AsyncService {

    @Async
    public CompletableFuture<String> asyncMethodWithException() {
        return CompletableFuture.supplyAsync(() -> {
            if (true) {
                throw new RuntimeException("Exception in async method");
            }
            return "Hello, World!";
        }).exceptionally(ex -> {
            System.out.println("Exception: " + ex.getMessage());
            return "Error occurred";
        });
    }
}

 


CompletableFuture - Java 표준의 강력한 비동기 처리

CompletableFuture는 Java 8에서 도입된 java.util.concurrent 패키지의 클래스이다. 비동기 작업을 수행하고 그 결과를 처리하는 데 사용되며, 다음과 같은 특징을 갖는다.


- 다양한 비동기 작업을 체이닝 하고 조합할 수 있는 유연한 API를 제공한다.
- CompletableFuture는 비동기 작업의 결과를 나타내는 객체로, 작업이 완료되면 결과를 얻을 수 있다.
- handle, exceptionally, whenComplete 등의 메서드를 사용하여 예외를 처리할 수 있다.
- thenApply, thenAccept, thenCompose 등의 메서드를 사용하여 비동기 작업을 체이닝 할 수 있다.

사용 예제

import java.util.concurrent.CompletableFuture;

public class CompletableFutureExample {

    public CompletableFuture<String> computeAsync() {
        return CompletableFuture.supplyAsync(() -> {
            // 비동기적으로 실행될 코드
            return "Hello, World!";
        });
    }

    public static void main(String[] args) {
        CompletableFutureExample example = new CompletableFutureExample();
        CompletableFuture<String> future = example.computeAsync();

        future.thenAccept(result -> {
            // 비동기 작업이 완료된 후 실행될 코드
            System.out.println(result);
        });

        // 예외 처리
        future.exceptionally(ex -> {
            System.out.println("Exception: " + ex.getMessage());
            return null;
        });
    }
}

 


차이점

사용 목적

@Async - Spring 애플리케이션에서 간단하게 비동기 메서드를 실행하고자 할 때 사용.
CompletableFuture - Java 표준 라이브러리를 사용하여 복잡한 비동기 작업을 체이닝 하고 조합할 때 사용.

설정 및 사용법

@Async - Spring 설정이 필요하며, 어노테이션을 통해 간단하게 비동기 메서드를 정의.
CompletableFuture - Java 표준 API를 사용하며, 더 많은 유연성과 기능을 제공.

스레드 관리

@Async - Spring의 TaskExecutor를 사용하여 스레드를 관리.
CompletableFuture - 기본적으로 ForkJoinPool.commonPool()을 사용하지만, 커스텀 Executor를 지정할 수 있음.

 


결론

이 두 가지 도구는 각각의 장단점이 있으므로, 상황에 맞게 선택하여 사용하는 것이 좋다. Spring 기반의 애플리케이션에서는 @Async를 사용하는 것이 더 간편할 수 있으며, 순수 Java 애플리케이션이나 더 복잡한 비동기 로직이 필요한 경우에는 CompletableFuture를 사용하는 것이 좋다.

 

댓글