๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
BackEnd๐ŸŒฑ/Spring

์Šคํ”„๋ง์—์„œ @Async๋ฅผ ์‚ฌ์šฉํ• ๋•Œ ์ฃผ์˜์ 

by dkswnkk 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๋งŒํผ์˜ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๊ณ  ํ•„์š”ํ•œ ๋งŒํผ ์ƒ์„ฑํ•˜๊ณ https://dkswnkk.tistory.com/745), ์Šค๋ ˆ๋“œ๋“ค์€ ์Šค๋ ˆ๋“œ ํ’€์ด ์ž‘์—…์„ ๋ฐ›์„ ๋•Œ๊นŒ์ง€ ๋Œ€๊ธฐ ์ƒํƒœ๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ๋งŒ์•ฝ ๋ชจ๋“  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() {
        // ๋กœ์ง
    }
}

๋”ฐ๋ผ์„œ, ์–ด๋–ค ๋ฐฉ๋ฒ•์„ ์„ ํƒํ• ์ง€๋Š” ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์˜ ์š”๊ตฌ ์‚ฌํ•ญ๊ณผ ํ”„๋กœ์ ํŠธ์˜ ๊ทœ๋ชจ์— ๋”ฐ๋ผ ๊ฒฐ์ •ํ•˜๋ฉด ๋ฉ๋‹ˆ๋‹ค.

๋Œ“๊ธ€