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

ThreadPoolExecutor ๋™์ž‘์— ๊ด€ํ•œ ์˜คํ•ด

by ์•ˆ์ฃผํ˜• 2024. 3. 11.

๊ฐœ์š”

Java์—์„œ ์Šค๋ ˆ๋“œํ’€์„ ํšจ๊ณผ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๊ฒŒ ํ•ด์ฃผ๋Š” ThreadPoolExecutor, ๊ทธ๋ฆฌ๊ณ  ์ด๋ฅผ ๋”์šฑ ์‰ฝ๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก Spring์—์„œ ์ œ๊ณตํ•˜๋Š” ThreadPoolTaskExecutor์˜ ๋™์ž‘ ๋ฐฉ์‹์— ๊ด€ํ•ด ์ œ๊ฐ€ ๊ทธ๋™์•ˆ ์ž˜๋ชป ์ดํ•ดํ•˜๊ณ  ์žˆ์—ˆ๋˜ ๋‘ ๊ฐ€์ง€์˜ ์˜คํ•ด๋ฅผ ์ •๋ฆฌํ•˜๊ณ ์ž ๊ธ€์„ ์ž‘์„ฑํ•˜๊ฒŒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.

๋‚ด์šฉ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  1. ๋ณ„๋„์˜ ์„ค์ •์„ ํ•˜์ง€ ์•Š์œผ๋ฉด ์„œ๋ฒ„ ์‹คํ–‰ ์‹œ์— ์ง€์ •ํ•œ ์Šค๋ ˆ๋“œ ํ’€ ํฌ๊ธฐ๋งŒํผ ์Šค๋ ˆ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค.
  2. corePoolSize์™€ maxPoolSize๋Š” ์„œ๋กœ ์ƒ๊ด€์—†๋‹ค.
    1. ํ์˜ ํฌ๊ธฐ๋ฅผ ์ง€์ •ํ•ด์ฃผ์ง€ ์•Š์œผ๋ฉด maxPoolSize์€ ์‚ฌ์‹ค์ƒ ์˜๋ฏธ ์—†๋Š” ์„ค์ •์ด๋‹ค.
    2. corePoolSizeํฌ๊ธฐ ์ด์ƒ์˜ ์š”์ฒญ์ด ๋“ค์–ด์˜จ๋‹ค๊ณ  ํ•ด์„œ maxPoolSize๋งŒํผ ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š”๋‹ค.

https://github.com/dkswnkk/ThreadPoolExecutorTest

 

 

์‚ฌ์ „์— ์Šค๋ ˆ๋“œํ’€์˜ ํฌ๊ธฐ๋งŒํผ ์Šค๋ ˆ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š๋Š”๋‹ค.

๋จผ์ € ์ฒซ ๋ฒˆ์งธ ์˜คํ•ด์— ๋Œ€ํ•ด ์‚ดํŽด๋ด…์‹œ๋‹ค. ์•„๋ž˜์™€ ๊ฐ™์€ ์„ค์ •์„ ๊ฐ€์ง„ ์Šค๋ ˆ๋“œ ํ’€์ด ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค.

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5); // ๊ธฐ๋ณธ ์Šค๋ ˆ๋“œ ์ˆ˜
        executor.setMaxPoolSize(20); // ์ตœ๋Œ€ ์Šค๋ ˆ๋“œ ์ˆ˜(default: Integer.MAX)
        executor.setQueueCapacity(11); // ํ ์šฉ๋Ÿ‰
        executor.setKeepAliveSeconds(60); // ์œ ํœด ์Šค๋ ˆ๋“œ ์ข…๋ฃŒ ์‹œ๊ฐ„(default: 60)
        executor.setThreadNamePrefix("Executor-"); // ์Šค๋ ˆ๋“œ ์ด๋ฆ„ ์ ‘๋‘์‚ฌ
        executor.initialize(); // ์ดˆ๊ธฐํ™”

        return executor;
    }
}

์œ„์™€ ๊ฐ™์ด ์„ค์ •ํ–ˆ์„ ๊ฒฝ์šฐ ์„œ๋ฒ„ ์‹œ์ž‘(์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰) ์‹œ ์Šค๋ ˆ๋“œ ํ’€์— corePoolSize์ธ 5๋งŒํผ ์ž๋™์œผ๋กœ ์Šค๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑ๋  ๊ฒƒ์œผ๋กœ ๊ธฐ๋Œ€ํ–ˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ์‹ค์ œ๋กœ๋Š” ์Šค๋ ˆ๋“œ ์ž‘์—…์ด ์š”์ฒญ๋  ๋•Œ ํ•„์š”ํ•œ ๋งŒํผ๋งŒ ์Šค๋ ˆ๋“œ๊ฐ€ ์Šค๋ ˆ๋“œํ’€์— ์ƒ์„ฑ๋˜๊ธฐ ์‹œ์ž‘ํ•ฉ๋‹ˆ๋‹ค.

์Šค๋ ˆ๋“œ ํ’€์˜ ๋™์ž‘์„ ํ™•์ธํ•˜๊ธฐ ์œ„ํ•œ ์ฝ”๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

@Slf4j
@SpringBootApplication
public class ThreadPoolTest {

    public static void main(String[] args) throws InterruptedException {
        ConfigurableApplicationContext context = SpringApplication.run(ThreadPoolTest.class, args);

        Run run = context.getBean(Run.class);
        for (int i = 0; i < 10; i++) {
            run.checkPrestartedThreads();
            Thread.sleep(1000);
        }
    }
}
@Slf4j
@Service
@DependsOn("threadPoolTaskExecutor")
public class Run {
    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Async(value = "threadPoolTaskExecutor")
    public void checkPrestartedThreads() {
        // ์Šค๋ ˆ๋“œ ํ’€์˜ ํ˜„์žฌ ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ ํ™•์ธ
        int poolSize = threadPoolTaskExecutor.getPoolSize();
        log.info("์Šค๋ ˆ๋“œ ํ’€์˜ ํ˜„์žฌ ์Šค๋ ˆ๋“œ ์ˆ˜: {}", poolSize);
    }
}

์œ„ ์ฝ”๋“œ์˜ ์ˆ˜ํ–‰ ๊ฒฐ๊ณผ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

์ž˜ ๋ณด๋ฉด ์ฒ˜์Œ๋ถ€ํ„ฐ ์Šค๋ ˆ๋“œ ํ’€์˜ ์Šค๋ ˆ๋“œ ์ˆ˜๊ฐ€ corePoolSize์ธ 5๋กœ ์„ธํŒ…๋˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ•ด๋‹น ์ž‘์—…์ด ์š”์ฒญ๋  ๋•Œ ์Šค๋ ˆ๋“œ ํ’€์— ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•˜๋ฉฐ, ํ•œ ๋ฒˆ์— corePoolSize๋งŒํผ์˜ ์ˆ˜๋ฅผ ์ƒ์„ฑํ•˜์ง€ ์•Š๊ณ  ํ•„์š”ํ•œ ๋งŒํผ ํ•˜๋‚˜์”ฉ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ตœ์ข…์ ์œผ๋กœ corePoolSize๋งŒํผ์˜ ์Šค๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑ๋˜์—ˆ์„ ๋•Œ ๋” ์ด์ƒ ์ƒ์„ฑํ•˜์ง€ ์•Š๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ฆ‰, ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹œ ์Šค๋ ˆ๋“œ ํ’€์— corePoolSize๋งŒํผ ์Šค๋ ˆ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋˜์ง€ ์•Š์œผ๋ฉฐ, ์ž‘์—… ์š”์ฒญ์ด ๋ฐœ์ƒํ•  ๋•Œ ์Šค๋ ˆ๋“œ๊ฐ€ ์ƒ์„ฑ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ํ•œ ๋ฒˆ์— corePoolSize๋งŒํผ ์ƒ์„ฑ๋˜์ง€ ์•Š๊ณ  ์š”์ฒญ์— ๋”ฐ๋ผ ์ ์ง„์ ์œผ๋กœ ์ƒ์„ฑ๋˜๋ฉฐ, ์Šค๋ ˆ๋“œ ํ’€์ด corePolSize์— ๋„๋‹ฌํ•˜๋ฉด ์ถ”๊ฐ€์ ์ธ ์Šค๋ ˆ๋“œ๋Š” ์ƒ์„ฑ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์„œ๋ฒ„ ์‹œ์ž‘ ํ›„, ์ฒซ ์š”์ฒญ์˜ ๊ฒฝ์šฐ์— cold start๊ฐ€ ์ผ์–ด๋‚˜ ์›ํ•˜๋˜ ์„ฑ๋Šฅ์ด ๋‚˜์˜ค์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ์Šต๋‹ˆ๋‹ค.

๋งŒ์•ฝ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์‹คํ–‰ ์‹œ ์Šค๋ ˆ๋“œ ํ’€์„ corePoolSize ๊ฐ’๋งŒํผ ์ฆ‰์‹œ ์ฑ„์šฐ๊ณ  ์‹ถ๋‹ค๋ฉด, ์•„๋ž˜์™€ ๊ฐ™์ด ThreadPoolTaskExecutor์˜ setPrestartAllCoreThreads๋ฅผ true๋กœ ์„ค์ •ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์Šค๋ ˆ๋“œ ํ’€์ด ์ดˆ๊ธฐํ™”๋  ๋•Œ(์„œ๋ฒ„ ์‹œ์ž‘ ์‹œ) corePoolSize์— ์„ค์ •๋œ ์ˆ˜๋งŒํผ ์Šค๋ ˆ๋“œ๋ฅผ ๋ฏธ๋ฆฌ ์ƒ์„ฑํ•˜๋„๋ก ์ง€์‹œํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

setPrestartAllCoreThreads์˜ default ๊ฐ’์€ false์ด๋ฉฐ, Spring์—์„œ ์ œ๊ณตํ•˜๋Š” ThreadPoolTaskExecutor์—๋Š” corePoolSize์— ์ง€์ •ํ•œ ์ˆ˜๋งŒํผ ํ•œ ๋ฒˆ์— ์Šค๋ ˆ๋“œ ํ’€์„ ์ฑ„์šฐ๋Š” setPrestartAllCoreThreads๋งŒ ์กด์žฌํ•˜์ง€๋งŒ Java์—์„œ ์ œ๊ณตํ•˜๋Š” ThreadTaskExecutor์—๋Š” setPrestartAllCoreThreads ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ prestartCoreThread๋„ ์กด์žฌํ•˜์—ฌ preStartCoreThread๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํšŸ์ˆ˜๋งŒํผ ์‚ฌ์ „์— ์Šค๋ ˆ๋“œ๋ฅผ ์ƒ์„ฑํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๋ฌผ๋ก  ์ด ๊ฒฝ์šฐ์—๋„ corePoolSize๋ฅผ ์ดˆ๊ณผํ•˜๋Š” ์Šค๋ ˆ๋“œ๋Š” ์ƒ์„ฑํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

 

 

corePoolSizeํฌ๊ธฐ์™€ maxPoolSizeํฌ๊ธฐ๋Š” ์„œ๋กœ ์ƒ๊ด€์—†๋‹ค.

์Šค๋ ˆ๋“œ ํ’€์˜ ๋™์ž‘ ํ”Œ๋กœ์šฐ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

https://medium.com/android-news/executor-framework-understanding-the-basics-43d575e72310

์Šค๋ ˆ๋“œ ํ’€์€ corePoolSize์˜ ์œ ํœด ์Šค๋ ˆ๋“œ๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” allowCoreThreadTimeOut ์„ค์ •์„ ๋ณ„๋„๋กœ ํ•˜์ง€ ์•Š์•˜์„ ์‹œ, corePoolSize์— ์ง€์ •๋œ ์ˆ˜ ์ดํ•˜์˜ ์ƒ์„ฑ๋œ ์Šค๋ ˆ๋“œ๋งŒ ๊ธฐ๋ณธ์ ์œผ๋กœ ์œ ์ง€ํ•ฉ๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  corePoolSize๋ฅผ ์ดˆ๊ณผํ•˜๋Š” ์š”์ฒญ์ด ๋“ค์–ด์˜ค๋”๋ผ๋„ ๊ณง๋ฐ”๋กœ maxPoolSize๋งŒํผ ์Šค๋ ˆ๋“œ๋ฅผ ๋Š˜๋ฆฌ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. maxPoolSize๋งŒํผ์˜ ์Šค๋ ˆ๋“œ ์ฆ๊ฐ€๋Š” setQueueCapacity๋กœ ์ง€์ •๋œ ์ž‘์—… ํ์˜ ํฌ๊ธฐ๊ฐ€ ๊ฐ€๋“ ์ฐผ์„ ๋•Œ์—๋งŒ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์ž‘์—… ํ๊ฐ€ ๊ฝ‰ ์ฐจ์•ผ์ง€๋งŒ ์Šค๋ ˆ๋“œ ํ’€์€ ์Šค๋ ˆ๋“œ ์ˆ˜๋ฅผ maxPoolSize๊นŒ์ง€ ์ฆ๊ฐ€์‹œํ‚ต๋‹ˆ๋‹ค.

๊ธฐ๋ณธ๊ฐ’์œผ๋กœ queueCapacity์˜ ํฌ๊ธฐ๋Š” Integer.MAX_VALUE๋กœ ์„ค์ •๋˜์–ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณ„๋„๋กœ queueCapacity๋ฅผ ์ง€์ •ํ•˜์ง€ ์•Š์œผ๋ฉด ์‚ฌ์‹ค์ƒ ํ์˜ ํฌ๊ธฐ๊ฐ€ ๊ฝ‰ ์ฐฐ ์ผ์ด ์—†์–ด ์Šค๋ ˆ๋“œ ํ’€์˜ ํฌ๊ธฐ๊ฐ€ maxPoolSize๊นŒ์ง€ ์ฆ๊ฐ€ํ•˜๋Š” ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์‹ค์ œ๋กœ ์•„๋ž˜์™€ ๊ฐ™์ด queue์˜ ํฌ๊ธฐ๋ฅผ default๋กœ ์„ค์ •ํ•œ ๊ฒฝ์šฐ, maxPoolSize๋ฅผ 20์œผ๋กœ ์ง€์ •ํ–ˆ์Œ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ , ์Šค๋ ˆ๋“œ ํ’€์˜ ํฌ๊ธฐ๊ฐ€ 5๋กœ ๊ณ„์†ํ•ด์„œ ์œ ์ง€๋˜๋Š” ๊ฒƒ์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

@Configuration
@EnableAsync
public class ThreadPoolConfig {

    @Bean(name = "threadPoolTaskExecutor")
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(20);
        executor.setThreadNamePrefix("Executor-");
        executor.initialize();

        return executor;
    }
}
@Slf4j
@Service
@DependsOn("threadPoolTaskExecutor")
public class Run {

    @Autowired
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;

    @Async(value = "threadPoolTaskExecutor")
    public void start() throws InterruptedException {
        // ์Šค๋ ˆ๋“œ ํ’€์˜ ํ˜„์žฌ ์Šค๋ ˆ๋“œ ์ˆ˜
        int poolSize = threadPoolTaskExecutor.getPoolSize();
        // ํ˜„์žฌ ํ™œ์„ฑ ์Šค๋ ˆ๋“œ ์ˆ˜
        int activeCount = threadPoolTaskExecutor.getActiveCount();
        // ์ž‘์—… ํ์˜ ๋‚จ์€ ์šฉ๋Ÿ‰
        int remainingCapacity = threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().remainingCapacity();
        // ํ˜„์žฌ ์ž‘์—… ํ์— ๋Œ€๊ธฐ ์ค‘์ธ ์ž‘์—… ์ˆ˜
        int queueSize = threadPoolTaskExecutor.getThreadPoolExecutor().getQueue().size();

        log.info("ํ˜„์žฌ ์Šค๋ ˆ๋“œ ์ด๋ฆ„: {}, ํ˜„์žฌ ์Šค๋ ˆ๋“œ ํ’€์˜ ์Šค๋ ˆ๋“œ ์ˆ˜: {} ํ˜„์žฌ ํ™œ์„ฑ ์Šค๋ ˆ๋“œ ์ˆ˜: {}, ํ˜„์žฌ ์ž‘์—… ํ์— ๋Œ€๊ธฐ ์ค‘์ธ ์ž‘์—… ์ˆ˜: {}, ์ž‘์—… ํ์˜ ๋‚จ์€ ํฌ๊ธฐ: {}",
                Thread.currentThread().getName(), poolSize, activeCount, queueSize, remainingCapacity);


        Thread.sleep(10000);
    }
}
@Slf4j
@SpringBootApplication
public class ThreadPoolTest {

    public static void main(String[] args) throws InterruptedException {
        ConfigurableApplicationContext context = SpringApplication.run(ThreadPoolTest.class, args);

        Run run = context.getBean(Run.class);
        for (int i = 0; i < 100; i++) {
            run.start();
        }
    }
}

๋”ฐ๋ผ์„œ ๋งŒ์•ฝ ํ์˜ ํฌ๊ธฐ๋ฅผ ๋ณ„๋„๋กœ ์„ค์ •ํ•˜์ง€ ์•Š์„ ๊ฒฝ์šฐ maxPoolSize ์„ค์ •์€ ์‚ฌ์‹ค์ƒ ์˜๋ฏธ๊ฐ€ ์—†๋Š” ๊ฐ’์ด๊ธฐ์—, coorPoolSize๋ฅผ ์ ์ ˆํ•˜๊ฒŒ ์„ค์ •ํ•˜๋Š” ๊ฒƒ์ด ์ค‘์š”ํ•ฉ๋‹ˆ๋‹ค.

๋Œ“๊ธ€