개발은 재밌어야 한다
article thumbnail
반응형

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


1. 1. @Async란 무엇인가?

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

1.1. 왜 @Async를 사용할까?

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

2. 2. @Async 설정

@Async를 사용하려면 설정이 필요합니다.

2.1. Step 1. @EnableAsync 활성화

Spring 애플리케이션에서 @Async를 활성화하려면 @EnableAsync 어노테이션을 추가합니다.

<java />
@Configuration @EnableAsync public class AsyncConfig { }​

2.2. Step 2. TaskExecutor 커스터마이징

Spring은 기본적으로 SimpleAsyncTaskExecutor를 사용합니다. 하지만 성능 최적화를 위해 커스텀 ThreadPoolTaskExecutor를 설정하는 것이 좋습니다.

<java />
@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; } }

 

2.3. TaskExecutor 설정의 의미

  1. CorePoolSize: 유지할 기본 스레드 수. (10개)
  2. MaxPoolSize: 최대 생성 가능한 스레드 수. (30개)
  3. QueueCapacity: 대기 중인 작업의 최대 개수. (100개)
  4. ThreadNamePrefix: 생성된 스레드 이름의 접두사. (예: async-1, async-2)

3. 3. @Async 기본 사용법

@Async를 메서드에 적용하면, 해당 메서드의 작업이 별도 스레드에서 실행됩니다.

3.1. 예제: 비동기로 이메일 알림 보내기

3.1.1. 1) 서비스 클래스

<java />
@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); } }​

3.1.2. 2) 컨트롤러 클래스

<java />
@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.1.3. 3) 동작 결과

클라이언트는 즉시 응답을 받고, 이메일 전송 작업은 별도의 스레드에서 처리됩니다.


4. 4. 비동기 작업의 반환값 처리

@Async 메서드는 void 외에도 CompletableFuture와 같은 반환 타입을 사용할 수 있습니다.

4.1. 예제: 비동기로 사용자 데이터 가져오기

4.1.1. 1) 서비스 클래스

<java />
@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); } }​

4.1.2. 2) 컨트롤러 클래스

 
<java />
@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; } }

4.1.3. 3) 동작 결과

  • 클라이언트는 비동기로 데이터를 받을 수 있습니다.
  • 이 방식은 비동기 작업을 호출하는 경우에도 유용합니다.

5. 5. 고급 활용 사례

5.1. 1) 대용량 데이터 병렬 처리

대량의 데이터를 병렬로 처리하여 작업 시간을 단축할 수 있습니다.

<java />
@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"); } }

5.2. 2) 멀티 TaskExecutor 활용

다양한 TaskExecutor를 정의하고, 특정 작업에 맞는 Executor를 사용할 수 있습니다.

<java />
@Async("customExecutor") public void performTaskWithCustomExecutor() { System.out.println("Task executed by custom executor"); }

6. 6. 주의사항

6.1. 1) Self-invocation 문제

동일 클래스 내에서 @Async 메서드를 호출하면 비동기로 동작하지 않습니다.
이를 해결하려면 별도 빈으로 선언된 객체에서 호출해야 합니다.

6.2. 2) 트랜잭션과의 충돌

@Transactional과 함께 사용할 경우, 비동기 스레드에서 트랜잭션 전파가 제대로 되지 않을 수 있습니다.
이를 고려한 설계가 필요합니다.

반응형
profile

개발은 재밌어야 한다

@ghyeong

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