λ³Έλ¬Έ λ°”λ‘œκ°€κΈ°
BackEnd🌱/Spring

μŠ€ν”„λ§μ—μ„œ @Asyncλ₯Ό μ‚¬μš©ν• λ•Œ 주의점

by μ•ˆμ£Όν˜• 2023. 7. 24.

κ°œμš”

비동기 ν”„λ‘œκ·Έλž˜λ°μ€ μ˜€λŠ˜λ‚ μ˜ μ†Œν”„νŠΈμ›¨μ–΄ κ°œλ°œμ—μ„œ 맀우 μ€‘μš”ν•œ κ°œλ…μž…λ‹ˆλ‹€. λŒ€μš©λŸ‰ 데이터 처리, 느린 I/O μž‘μ—…, λ³΅μž‘ν•œ 계산 λ“± λ‹€μ–‘ν•œ μž‘μ—…μ„ λ³‘λ ¬λ‘œ μ²˜λ¦¬ν•˜λ©΄μ„œ μ‹œμŠ€ν…œμ˜ 응닡 μ‹œκ°„μ„ κ°œμ„ ν•˜κ³ , λ¦¬μ†ŒμŠ€λ₯Ό 효율적으둜 μ‚¬μš©ν•˜λŠ”λ° 도움이 되기 λ•Œλ¬Έμž…λ‹ˆλ‹€.

μŠ€ν”„λ§μ—μ„œλŠ” @Async μ–΄λ…Έν…Œμ΄μ…˜μ„ 톡해 μ΄λŸ¬ν•œ 비동기 λ©”μ„œλ“œλ₯Ό κ°„λ‹¨ν•˜κ²Œ μ‹€ν–‰ν•  수 μžˆλŠ”λ°, μ΄λŸ¬ν•œ @Async μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•  λ•Œ μ£Όμ˜ν•΄μ•Ό ν•  μ—¬λŸ¬ 가지 사항에 λŒ€ν•΄μ„œ 정리해 λ³΄κ² μŠ΅λ‹ˆλ‹€.
 

 

주의점

  1. Exception Handling
  2. λ©”μ„œλ“œ 호좜
  3. 리턴 νƒ€μž…
  4. νŠΈλžœμž­μ…˜ 관리
  5. Execution

 

 

Exception Handling

기본적으둜 @Async λ©”μ„œλ“œμ—μ„œ λ°œμƒν•˜λŠ” μ˜ˆμ™ΈλŠ” ν˜ΈμΆœμžμ—κ²Œ μ „νŒŒκ°€ λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ΄λŠ” @Async μ–΄λ…Έν…Œμ΄μ…˜μ΄ 뢙은 λ©”μ„œλ“œκ°€ λ³„λ„μ˜ μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜λ―€λ‘œ 메인 μŠ€λ ˆλ“œμ—μ„œ 캐치λ₯Ό ν•  수 μ—†κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€. μ˜ˆμ™Έλ₯Ό μ²˜λ¦¬ν•˜κΈ° μœ„ν•΄μ„œλŠ” AsyncUncaughtExceptionHandlerλ₯Ό μ‚¬μš©ν•˜μ—¬ μ˜ˆμ™Έλ₯Ό 적절히 μ²˜λ¦¬ν•΄μ•Ό ν•©λ‹ˆλ‹€.


AsyncConfigurerSupportλ₯Ό μƒμ†λ°›λŠ” 클래슀

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new MyAsyncUncaughtExceptionHandler();
    }
}

AsyncUncaughtExceptionHandlerλ₯Ό κ΅¬ν˜„ν•˜λŠ” 클래슀

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);
        }
    }
}

μœ„μ™€ 같이 AsyncConfigurerSupportλ₯Ό 상속받고, getAsyncUncaughtExceptionHandler()λ₯Ό μ˜€λ²„λΌμ΄λ”©ν•˜μ—¬ μ‚¬μš©μžκ°€ μ •ν•œ μ˜ˆμ™Έ ν•Έλ“€λŸ¬λ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
 

 

λ©”μ„œλ“œ 호좜

@Service
public class xxxService {
    public void internalCall() {
        this.asyncMethod(); // λ™κΈ°μ μœΌλ‘œ 싀행됨
    }
    
    @Async
    public void asyncMethod() {
        // λΉ„λ™κΈ°λ‘œ μ²˜λ¦¬λ˜μ–΄μ•Ό ν•˜λŠ” 둜직
    }
}

μœ„ μ½”λ“œμ²˜λŸΌ μŠ€ν”„λ§μ—μ„œ @Async μ–΄λ…Έν…Œμ΄μ…˜μ΄ 뢙은 λ©”μ„œλ“œλŠ” 같은 클래슀 λ‚΄λΆ€μ—μ„œ 직접 ν˜ΈμΆœν•  경우, λ³„λ„μ˜ μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. μ΄λŠ” μŠ€ν”„λ§μ˜ AOPκ°€ ν”„λ‘μ‹œ 기반으둜 λ™μž‘ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.

이 문제λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄μ„œλŠ” λ³„λ„μ˜ λΉˆμ„ μƒμ„±ν•˜κ±°λ‚˜, λ‹€λ₯Έ ν΄λž˜μŠ€μ—μ„œ ν˜ΈμΆœν•΄μ•Ό ν•˜λ©° 같은 ν΄λž˜μŠ€μ—μ„œ ν˜ΈμΆœν•˜λ €λ©΄ μ•„λž˜μ²˜λŸΌ self referenceλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•©λ‹ˆλ‹€.

@Service
public class SelfInvocationService {
    @Autowired
    private SelfInvocationService self;

    public void method() {
        self.asyncMethod(); // self-invocation으둜 λΉ„λ™κΈ°μ μœΌλ‘œ 싀행됨
    }
    
    @Async
    public void asyncMethod() {
        // λΉ„λ™κΈ°λ‘œ μ²˜λ¦¬λ˜μ–΄μ•Ό ν•˜λŠ” 둜직
    }
}

μœ„ μ½”λ“œμ—μ„œ method()λŠ” self.asyncMethod();λ₯Ό 톡해 비동기 λ©”μ„œλ“œλ₯Ό ν˜ΈμΆœν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. selfλŠ” SelfInvocationService의 μΈμŠ€ν„΄μŠ€λ‘œ, μŠ€ν”„λ§ μ»¨ν…Œμ΄λ„ˆμ— μ˜ν•΄ ν”„λ‘μ‹œκ°€ μ£Όμž…λ©λ‹ˆλ‹€. μ΄λ ‡κ²Œ 되면 asyncMethod()κ°€ λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λ©λ‹ˆλ‹€.

 

 

리턴 νƒ€μž…

@Async μ–΄λ…Έν…Œμ΄μ…˜μ΄ 뢙은 λ©”μ„œλ“œλŠ” void, Future, CompletableFuture 이 쀑 ν•˜λ‚˜μ˜ λ°˜ν™˜ νƒ€μž…μ„ κ°€μ Έμ•Ό ν•©λ‹ˆλ‹€. λ§Œμ•½ Futureλ₯Ό λ°˜ν™˜ν•˜λŠ” 경우, 비동기 μž‘μ—…μ˜ κ²°κ³Όλ₯Ό λ°˜ν™˜ν•˜κ³ , ν•΄λ‹Ή μž‘μ—…μ˜ μ™„λ£Œλ₯Ό λŒ€κΈ°ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ•„λž˜ μ½”λ“œλŠ” 이λ₯Ό λ³΄μ—¬μ£ΌλŠ” μ˜ˆμ‹œμž…λ‹ˆλ‹€.

@Service
public class AsyncService {
    @Async
    public Future<String> asyncMethodWithReturnType() {
        System.out.println("Execute method asynchronously - " + Thread.currentThread().getName());
        try {
            Thread.sleep(5000);
            return new AsyncResult<>("hello world !!!!");
        } catch (final InterruptedException e) {
            //
        }
        return null;
    }
}

μœ„μ˜ λ©”μ„œλ“œλŠ” λΉ„λ™κΈ°μ μœΌλ‘œ μ‹€ν–‰λ˜λ©°, μž‘μ—…μ΄ μ™„λ£Œλ  λ•ŒκΉŒμ§€ 기닀리지 μ•Šκ³  μ¦‰μ‹œ Future 객체λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€. 그리고 이 Future객체λ₯Ό 톡해 비동기 μž‘μ—…μ˜ κ²°κ³Όλ₯Ό κ°€μ Έμ˜¬ 수 μžˆμŠ΅λ‹ˆλ‹€.

κ·ΈλŸ¬λ‚˜ Futureλ‚˜ CompletableFuture 객체λ₯Ό λ°˜ν™˜λ°›μ•„λ„, 비동기 연산이 아직 μ™„λ£Œλ˜μ§€ μ•Šμ•˜μ„ 경우  λ°˜ν™˜κ°’에 μ‹€μ œ 값듀은 담기지 μ•Šμ•˜μ„ μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.

@Service
public class xxxService {
    @Autowired
    private AsyncService asyncService;

    public void method() {
        Future<String> future = asyncService.asyncMethodWithReturnType();
        String result = future.get(); // This will block
        System.out.println(result);
    }
}

ν•΄κ²° λ°©λ²•μœΌλ‘œλŠ”, μœ„μ™€ 같이 Future의 get() λ©”μ„œλ“œλ₯Ό 톡해 비동기 μž‘μ—…μ΄ 끝날 λ•ŒκΉŒμ§€(값이 λ‹΄κΈΈ λ•ŒκΉŒμ§€) ν˜„μž¬ μŠ€λ ˆλ“œλ₯Ό λΈ”λ‘œν‚Ήν•˜λŠ” 방법이 μžˆμŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ΄λŠ” 비동기 λ©”μ„œλ“œλ₯Ό λ™κΈ°μ μœΌλ‘œ λ™μž‘ν•˜κ²Œ λ§Œλ“€μ–΄, μ›λž˜ λΉ„λ™κΈ°λ‘œ μ„€κ³„λœ μ½”λ“œμ˜ 이점을 λˆ„λ¦¬μ§€ λͺ»ν•˜κ²Œ λ©λ‹ˆλ‹€.

λ”°λΌμ„œ Futureλ‚˜ CompletableFuture의 κ²°κ³Όλ₯Ό μ²˜λ¦¬ν•  λ•ŒλŠ” μ£Όμ˜κ°€ ν•„μš”ν•œλ°, κ°€λŠ₯ν•˜λ©΄ non-blocking λ°©μ‹μœΌλ‘œ μ ‘κ·Όν•˜κ±°λ‚˜, ν•„μš”ν•  경우 μ½œλ°±μ„ μ‚¬μš©ν•˜μ—¬ μ²˜λ¦¬ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€. μ΄λŠ” CompletableFuture의 thenApply, thenAccept λ“±μ˜ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•˜μ—¬ κ΅¬ν˜„ν•  수 있으며, μ΄λ ‡κ²Œ ν•˜λ©΄ 비동기 μ—°μ‚° 결괏값이 담겼을 λ•Œ λΉ„λ™κΈ°μ μœΌλ‘œ κ²°κ³Όλ₯Ό μ²˜λ¦¬ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
 

 

νŠΈλžœμž­μ…˜ 관리

비동기 λ©”μ„œλ“œμ—μ„œ νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•  λ•ŒλŠ” μ£Όμ˜κ°€ ν•„μš”ν•©λ‹ˆλ‹€. @Async μ–΄λ…Έν…Œμ΄μ…˜μ΄ 뢙은 λ©”μ„œλ“œλŠ” ν˜ΈμΆœν•œ λ©”μ„œλ“œμ™€ 독립적인 μŠ€λ ˆλ“œμ—μ„œ λ™μž‘ν•˜κΈ° λ•Œλ¬Έμ—, 비동기 λ©”μ„œλ“œ λ‚΄μ—μ„œ μƒμ„±λœ νŠΈλžœμž­μ…˜μ€ ν˜ΈμΆœν•œ λ©”μ„œλ“œμ˜ νŠΈλžœμž­μ…˜κ³ΌλŠ” λ³„κ°œμ˜ 생λͺ…μ£ΌκΈ°λ₯Ό κ°€μ§‘λ‹ˆλ‹€.

즉, 비동기 λ©”μ„œλ“œ λ‚΄μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ—¬ νŠΈλžœμž­μ…˜μ΄ λ‘€λ°±λ˜μ–΄μ•Ό ν•  κ²½μš°μ—λ„, μ΄λŠ” ν•΄λ‹Ή νŠΈλžœμž­μ…˜μ„ μƒμ„±ν•œ λ³„λ„μ˜ μŠ€λ ˆλ“œμ—μ„œ λ…λ¦½μ μœΌλ‘œ μ²˜λ¦¬λ˜λ―€λ‘œ, μ›λž˜μ˜ 호좜 λ©”μ„œλ“œμ˜ νŠΈλžœμž­μ…˜μ—λŠ” 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

@Service
public class TransactionalService {
    @Autowired
    private AsyncService asyncService;

    @Transactional
    public void transactionalMethod() {
        asyncService.asyncMethodWithNewTransaction();
    }
}

μœ„ μ½”λ“œμ—μ„œ, transactionalMethod()λŠ” @Transactional μ–΄λ…Έν…Œμ΄μ…˜μ΄ λΆ™μ–΄ νŠΈλžœμž­μ…˜ λ²”μœ„ λ‚΄μ—μ„œ μ‹€ν–‰λ˜μ§€λ§Œ, asyncMethodWithNewTransaction()은 @Async μ–΄λ…Έν…Œμ΄μ…˜μ΄ λΆ™μ–΄ μƒˆλ‘œμš΄ μŠ€λ ˆλ“œμ—μ„œ μ‹€ν–‰λ˜λ―€λ‘œ λ³„λ„μ˜ νŠΈλžœμž­μ…˜μ„ μƒμ„±ν•©λ‹ˆλ‹€.

λ”°λΌμ„œ asyncMethodWithNewTransaction()μ—μ„œ μ˜ˆμ™Έκ°€ λ°œμƒν•˜μ—¬ 둀백이 ν•„μš”ν•˜λ”λΌλ„, μ΄λŠ” asyncMethodWithNewTransaction()λ₯Ό μ‹€ν–‰ν•˜λŠ” λ³„λ„μ˜ μŠ€λ ˆλ“œμ—μ„œ μ²˜λ¦¬λ˜λ―€λ‘œ transactionalMethod()의 νŠΈλžœμž­μ…˜μ—λŠ” 영ν–₯을 λ―ΈμΉ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 

 

Executor 

μŠ€ν”„λ§μ—μ„œ 비동기 μž‘μ—…μ„ μˆ˜ν–‰ν•˜λ €λ©΄ 보톡 @EnableAsync μ–΄λ…Έν…Œμ΄μ…˜κ³Ό @Asyncλ₯Ό μ‚¬μš©ν•©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ μ΄λ ‡κ²Œ μ„€μ •ν•˜λ©΄ 기본적으둜 SimpleAsyncTaskExecutorκ°€ μ‚¬μš©λ˜λŠ”λ°, μ΄λŠ” 비동기 μž‘μ—…λ§ˆλ‹€ μƒˆλ‘œμš΄ μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜λŠ” μŠ€λ ˆλ“œν’€μž…λ‹ˆλ‹€. 이둜 인해 λ¦¬μ†ŒμŠ€ λ‚­λΉ„, μ„±λŠ₯ μ €ν•˜, μŠ€μΌ€μΌλ§ 문제 등이 λ°œμƒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

  • λ¦¬μ†ŒμŠ€ λ‚­λΉ„: 각 비동기 μž‘μ—…λ§ˆλ‹€ μƒˆλ‘œμš΄ μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜λ―€λ‘œ, λ™μ‹œμ— λ§Žμ€ 비동기 μž‘μ—…μ΄ μš”μ²­λ˜λ©΄ 맀번 λ§Žμ€ μŠ€λ ˆλ“œκ°€ μƒμ„±λœλ‹€. λ”°λΌμ„œ CPU와 λ©”λͺ¨λ¦¬ λ¦¬μ†ŒμŠ€μ˜ μ‚¬μš©λŸ‰μ΄ κ³Όλ„ν•˜κ²Œ μ¦κ°€ν•˜λŠ” λ¬Έμ œκ°€ λ°œμƒν•  수 μžˆλ‹€.
  • μ„±λŠ₯ μ €ν•˜: μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜κ³  μ†Œλ©Έμ‹œν‚€λŠ” λ°λŠ” λ§Žμ€ μ‹œκ°„κ³Ό λ¦¬μ†ŒμŠ€κ°€ μ†Œμš”λœλ‹€. 각 μž‘μ—…λ§ˆλ‹€ μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜λ©΄ 이런 μ˜€λ²„ν—€λ“œκ°€ 계속 λ°œμƒν•˜κ²Œ 되고, 전체적인 μ‹œμŠ€ν…œ μ„±λŠ₯에 영ν–₯을 끼칠 수 μžˆλ‹€.
  • μŠ€μΌ€μΌλ§ 문제: SimpleAsyncTaskExecutorλŠ” μŠ€λ ˆλ“œ μˆ˜μ— λŒ€ν•œ μ œν•œμ΄ μ—†λ‹€. λ”°λΌμ„œ λ™μ‹œμ— λ§Žμ€ μš”μ²­μ΄ λ“€μ–΄μ˜¬ 경우 μŠ€λ ˆλ“œ μˆ˜κ°€ λ¬΄ν•œμ •μœΌλ‘œ μ œμ–΄ν•  수 μ—†λŠ” μˆ˜μ€€μœΌλ‘œ 증가할 수 있으며, μ΄λŠ” 곧 OutOfMemoryError λ“±μ˜ 문제λ₯Ό μΌμœΌν‚¬ 수 μžˆλ‹€.

μ΄λŸ¬ν•œ 이유둜, νŠΉλ³„ν•œ κ²½μš°κ°€ μ•„λ‹ˆλΌλ©΄ SimpleAsyncTaskExecutor λ³΄λ‹€λŠ” ThreadPoolTaskExecutor 같은 μ œν•œλœ λ¦¬μ†ŒμŠ€λ₯Ό μ‚¬μš©ν•˜λŠ” μŠ€λ ˆλ“œν’€μ„ μ‚¬μš©ν•˜λŠ” 것이 μ’‹μŠ΅λ‹ˆλ‹€.

ThreadPoolTaskExecutorλŠ” Java의 ThreadPoolExecutorλ₯Ό Wrapping ν•œ κ²ƒμœΌλ‘œ, μŠ€ν”„λ§μ—μ„œ μ œκ³΅ν•˜λŠ” TaskExecutor μΈν„°νŽ˜μ΄μŠ€μ˜ κ΅¬ν˜„μ²΄μž…λ‹ˆλ‹€. Executorλ₯Ό μ •μ˜ν•˜λŠ” λ°©λ²•μ—λŠ” μ•„λž˜μ˜ 두 가지 방법이 μžˆμŠ΅λ‹ˆλ‹€.

  • Bean으둜 μ •μ˜
  • AsyncConfigurerSupportλ₯Ό 상속받아 getAsyncExecutor()λ₯Ό μž¬μ •μ˜

1. 직접 Bean으둜 Executorλ₯Ό μ •μ˜

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

각각의 μ˜΅μ…˜ 섀정에 λŒ€ν•œ μ„€λͺ…은 λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

  • setCorePoolSize
    • μŠ€λ ˆλ“œ 풀이 싀행을 μ‹œμž‘ν•  λ•Œ μƒμ„±λ˜λŠ” μŠ€λ ˆλ“œμ˜ 수λ₯Ό μ •μ˜ν•˜λ©°, μŠ€λ ˆλ“œ ν’€μ˜ κΈ°λ³Έ μ‚¬μ΄μ¦ˆλ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.
    • μŠ€λ ˆλ“œ 풀은 μž‘μ—…μ΄ λ“€μ–΄μ˜€κΈ° μ‹œμž‘ν•˜λ©΄ μ¦‰μ‹œ corePoolSize만큼의 μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜κ³ , μŠ€λ ˆλ“œλ“€μ€ μŠ€λ ˆλ“œ 풀이 μž‘μ—…μ„ 받을 λ•ŒκΉŒμ§€ λŒ€κΈ° μƒνƒœλ‘œ μœ μ§€λ©λ‹ˆλ‹€. λ§Œμ•½ λͺ¨λ“  core μŠ€λ ˆλ“œκ°€ μž‘μ—…μ„ μ²˜λ¦¬ν•˜κ³  있고 μΆ”κ°€ μž‘μ—…μ΄ λ“€μ–΄μ˜¨ 경우, μž‘μ—…μ€ 큐에 λ°°μΉ˜λ˜κ±°λ‚˜ maxPoolSizeκΉŒμ§€ μŠ€λ ˆλ“œκ°€ μΆ”κ°€λ‘œ μƒμ„±λ©λ‹ˆλ‹€.
  • setMaxPoolSize
    • μŠ€λ ˆλ“œ 풀이 ν™•μž₯될 수 μžˆλŠ” μŠ€λ ˆλ“œμ˜ μƒν•œμ„ μ„ μ„€μ •ν•˜λ©°, μŠ€λ ˆλ“œ 풀이 관리할 수 μžˆλŠ” μ΅œλŒ€ μŠ€λ ˆλ“œ 수λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. λ§Œμ•½ corePoolSizeλ₯Ό μ΄ˆκ³Όν•˜λŠ” μž‘μ—…μ΄ λ“€μ–΄μ˜¬ 경우, μ΄λŸ¬ν•œ μΆ”κ°€ μž‘μ—…λ“€μ€ λ¨Όμ € 큐에 λ°°μΉ˜λ©λ‹ˆλ‹€. κ·ΈλŸ¬λ‚˜ λŒ€κΈ°μ—΄μ΄ 가득 μ°¨λ©΄ μŠ€λ ˆλ“œ 풀은 maxPoolSize에 도달할 λ•ŒκΉŒμ§€ μΆ”κ°€ μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜μ—¬ μž‘μ—…μ„ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
    • maxPoolSize에 λ„λ‹¬ν•˜λ©΄ μŠ€λ ˆλ“œ 풀은 μƒˆ μž‘μ—…μ„ 받아듀이지 μ•ŠμœΌλ©°, λŒ€μ‹  정책에 따라 κ±°λΆ€ν•˜κ±°λ‚˜ λ‹€λ₯Έ λ°©λ²•μœΌλ‘œ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
  • setQueueCapacity
    • corePoolSizeκ°€ 가득 μ°¬ μƒνƒœμ—μ„œ μΆ”κ°€ μž‘μ—…μ„ μ²˜λ¦¬ν•  수 없을 λ•Œ λŒ€κΈ°ν•˜λŠ” μž‘μ—…μ˜ μ΅œλŒ€ 개수λ₯Ό μ •μ˜ν•˜λ©°, μŠ€λ ˆλ“œ ν’€μ˜ μž‘μ—… λŒ€κΈ°μ—΄ 크기λ₯Ό μ„€μ •ν•©λ‹ˆλ‹€.
    • μž‘μ—… λŒ€κΈ°μ—΄μ€ corePoolSize에 μžˆλŠ” μŠ€λ ˆλ“œλ“€μ΄ λͺ¨λ‘ μ‚¬μš© 쀑일 λ•Œ μΆ”κ°€ μž‘μ—…μ„ μž„μ‹œ μ €μž₯ν•˜λŠ” κ³΅κ°„μž…λ‹ˆλ‹€. λŒ€κΈ°μ—΄μ˜ 크기가 꽉 μ°¨λ©΄, μŠ€λ ˆλ“œ 풀은 maxPoolSizeκΉŒμ§€ μŠ€λ ˆλ“œλ₯Ό μƒμ„±ν•˜μ—¬ λŒ€κΈ°μ—΄μ— μžˆλŠ” μž‘μ—…μ„ μ²˜λ¦¬ν•˜κΈ° μ‹œμž‘ν•©λ‹ˆλ‹€. λŒ€κΈ°μ—΄μ΄ 가득 μ°¨κ³  maxPoolSize에 λ„λ‹¬ν•˜λ©΄ μŠ€λ ˆλ“œ 풀은 μƒˆλ‘œμš΄ μž‘μ—…μ„ μ–΄λ–»κ²Œ μ²˜λ¦¬ν• μ§€ κ²°μ •ν•΄μ•Ό ν•˜λ©°, 일반적으둜 κ±°λΆ€ μ •μ±…(RejectedExecutionHandler)을 μ‚¬μš©ν•˜μ—¬ μΆ”κ°€ μž‘μ—…μ„ μ²˜λ¦¬ν•©λ‹ˆλ‹€.
  • setThreadNamePrefix
    • μƒμ„±λ˜λŠ” μŠ€λ ˆλ“œμ˜ 이름 접두사λ₯Ό μ •μ˜ν•©λ‹ˆλ‹€. 이λ₯Ό 톡해 λ””λ²„κΉ…μ΄λ‚˜ λ‘œκΉ…μ—μ„œ μ–΄λ–€ μŠ€λ ˆλ“œκ°€ μž‘μ—…μ„ μ²˜λ¦¬ν•˜κ³  μžˆλŠ”μ§€ μ‰½κ²Œ 식별할 수 μžˆλŠ”λ°, ν˜„μž¬ μ˜ˆμ‹œμ²˜λŸΌ μž‘μ„±ν•˜λ©΄ 각 μŠ€λ ˆλ“œμ˜ 이름이 Executor-1, Executor-2와 같이 μ„€μ •λ©λ‹ˆλ‹€.
  • initialize()
    • μŠ€λ ˆλ“œ 풀을 μ΄ˆκΈ°ν™”ν•©λ‹ˆλ‹€. initialize()κ°€ 호좜되면, μ„€μ •λœ corePoolSize만큼의 μŠ€λ ˆλ“œκ°€ μ¦‰μ‹œ μƒμ„±λ©λ‹ˆλ‹€.

μ΄λ ‡κ²Œ μ„€μ •ν•œ ν›„, @Async μ–΄λ…Έν…Œμ΄μ…˜μ— μ•„λž˜μ²˜λŸΌ Executor의 Bean 이름을 인자둜 μ „λ‹¬ν•˜λ©΄ λ©λ‹ˆλ‹€. 

@Service
public class xxxService {

    @Async("threadPoolTaskExecutor")
    public void asyncMethod() {
        // λΉ„λ™κΈ°λ‘œ μ²˜λ¦¬λ˜μ–΄μ•Ό ν•˜λŠ” 둜직
    }
}

이 방법은 ThreadPoolTaskExecutorλ₯Ό 직접 Bean으둜 λ“±λ‘ν•©λ‹ˆλ‹€. λ”°λΌμ„œ @Async μ–΄λ…Έν…Œμ΄μ…˜μ— Bean 이름을 직접 지정해쀄 수 μžˆμŠ΅λ‹ˆλ‹€. 이 λ°©μ‹μ˜ μž₯점은 μ—¬λŸ¬ 개의 Executorλ₯Ό 각각 Bean으둜 λ“±λ‘ν•˜κ³ , 상황에 맞게 μ μ ˆν•œ Executorλ₯Ό 선택할 수 μžˆλ‹€λŠ” μ μž…λ‹ˆλ‹€. 즉, 비동기 μž‘μ—…μ˜ μ’…λ₯˜λ‚˜ λ³΅μž‘λ„μ— 따라 λ‹€λ₯Έ Executorλ₯Ό μ‚¬μš©ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

 

μ˜ˆμ‹œ

@Configuration
@EnableAsync
public class AsyncConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor threadPoolTaskExecutor() {
        //...
    }

    @Bean(name = "anotherExecutor")
    public Executor anotherExecutor() {
        //...
    }
}
@Service
public class xxxService {

    @Async("threadPoolTaskExecutor")
    public void asyncMethod() {
        // 둜직
    }

    @Async("anotherExecutor")
    public void anotherAsyncMethod() {
        // λ‹€λ₯Έ 둜직
    }
}

 

2. AsyncConfigurerSupportλ₯Ό 상속받아 getAsyncExecutor()λ₯Ό μž¬μ •μ˜

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

이 방법은 μŠ€ν”„λ§μ˜ κΈ°λ³Έ Executorλ₯Ό μž¬μ •μ˜ν•˜μ—¬ μ‚¬μš©ν•˜λŠ” λ°©λ²•μœΌλ‘œ, λ³„λ„μ˜ 이름을 μ§€μ •ν•˜μ§€ μ•Šμ•„λ„ λ©λ‹ˆλ‹€. 이 방식은 전역적인 Executor 섀정에 μœ μš©ν•˜λ©°, ν”„λ‘œμ νŠΈ μ „μ²΄μ—μ„œ 단일 Executorλ₯Ό μ‚¬μš©ν•˜λ €λŠ” κ²½μš°μ— μ ν•©ν•©λ‹ˆλ‹€.
 

μ˜ˆμ‹œ

@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        //...
        return executor;
    }
}
@Service
public class xxxService {

    @Async
    public void asyncMethod() {
        // 둜직
    }
}

λ”°λΌμ„œ, μ–΄λ–€ 방법을 μ„ νƒν• μ§€λŠ” λΉ„μ¦ˆλ‹ˆμŠ€ 둜직의 μš”κ΅¬ 사항과 ν”„λ‘œμ νŠΈμ˜ 규λͺ¨μ— 따라 κ²°μ •ν•˜λ©΄ λ©λ‹ˆλ‹€.

 

 

κ²°λ‘ 

비동기 ν”„λ‘œκ·Έλž˜λ°μ€ μ‹œμŠ€ν…œμ˜ μ„±λŠ₯을 ν–₯μƒμ‹œν‚€λŠ” 데 맀우 μœ μš©ν•˜μ§€λ§Œ, μ£Όμ˜ν•΄μ•Ό ν•  점듀이 λ§ŽμŠ΅λ‹ˆλ‹€. νŠΉνžˆλ‚˜ μŠ€ν”„λ§μ—μ„œ @Async μ–΄λ…Έν…Œμ΄μ…˜μ„ μ‚¬μš©ν•˜μ—¬ 비동기 λ©”μ„œλ“œλ₯Ό λ§Œλ“€ λ•ŒλŠ” μœ„μ—μ„œ λ§ν•œ Execption Handling, λ©”μ„œλ“œ 호좜, 리턴 νƒ€μž…, νŠΈλžœμž­μ…˜ 관리, Execution 등을 적절히 κ³ λ €ν•΄μ•Ό ν•©λ‹ˆλ‹€. 
 

 

μ°Έκ³ 

λŒ“κΈ€