티스토리 뷰

728x90
반응형

ThreadPoolTaskExecutor는 org.springframework.scheduling.concurrent 패키지에 속해있습니다.

 

ThreadPoolTaskExecutor를 사용한 예제 코드

로그가 출력된 시간을 보면 1초마다 출력이 되고있고 10번의 출력이 다 된것을 알 수 있습니다. 로그에서 볼 수 있듯이 아래 예제는 멀티 쓰레드로 돌아간게 아닌 하나의 쓰레드로 돌아간것을 알 수 있습니다. 그 이유는 ThreadPoolTaskExecutor는 기본적으로  동시에 실행할 쓰레드의 갯수가 1로 설정되어 있습니다.

public class Example {
    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.initialize();

        Runnable thread = () -> {
            try {
                System.out.println(Thread.currentThread().getName() + ", Now sleeping 1 seconds...");
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < 10; i++) {
            executor.execute(thread);
        }
    }
}

 

ThreadPoolTaskExecutor의 옵션 값


💡setCorePoolSize (default : 1)

  • 동시에 실행시킬 쓰레드의 갯수를 의미합니다.

💡setMaxPoolSize (default : Integer.MAX_VALUE)

  • 쓰레드 풀의 최대 사이즈를 설정합니다. 

💡setQueueCapacity (default : Integer.MAX_VALUE)

  • 쓰레드 풀의 큐 사이즈
  • corePoolSize의 개수를 넘어서는 task가 들어왔을 경우 queue에 해당 task들이 쌓이게 됩니다. 

💡setKeepAliveSeconds (default : 60)

  • maxPoolSize가 모두 사용되다가 idle로 돌아갔을 때 종료하기까지 대기하는데 걸리는 시간입니다.
  • 예를 들어 CorePoolSize가 5이며, MaxPoolSize가 15라는 가정하에 요청이 많아져서 thread pool이 바빠져서 추가적으로 10개의 스레드를 생성해서 총 15개의 스레드를 사용하게 됩니다. 그리고 바쁜 타임지 지나 한가해지면 스레드는 idle 상태가 됩니다. 
    그리고 자원을 절약하기 위해 한가한 상태의 쓰레드는 죽게 됩니다.
  • 스레드가 idle 상태에서 죽게되는 상태가 되기까지 대기하는 시간을 setKeepAliveSeconds() 옵션으로 지정할 수 있습니다.

💡setWaitForTasksToCompleteOnShutdown (default : false)

  • 시스템을 종료할 때 queue에 남아있는 작업을 모두 완료한 후에 종료하도록 처리합니다.

💡setAwaitTerminationSeconds (default : 0)

  • 시스템을 종료할 때 queue에 남아있는 작업을 모두 완료한 후 종료하도록 처리하거나 타임아웃을 지정해 해당 시간이 넘으면 강제종료 시킵니다.
public TaskExecutor taskExecutor() {
    ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    taskExecutor.setCorePoolSize(5);	// 기본 스레드 수
    taskExecutor.setMaxPoolSize(10);	// 최대 스레드 수
    taskExecutor.setQueueCapacity(100);	// Queue 사이즈
    return taskExecutor;
}

위와 같은 설정에서는 5개의 스레드로 처리하다가 처리 속도가 밀리는 경우 100개 사이즈 큐에 대기하고 있다가 그 보다 많은 요청이 발생하는 경우 최대 10개의 스레드까지 생성하여 처리하게 됩니다.

 

 

setCorePoolSize()를 3으로 설정

아래 코드가 실행되면 실행되자마자 3개의 쓰레드가 실행되고 10초 뒤에 3개의 쓰레드가 실행되는 것을 알 수 있습니다. 

@Slf4j
public class Example {
    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(3); // 3으로 설정
        executor.initialize();

        Runnable thread = () -> {
            try {
                log.info(Thread.currentThread().getName() + ", Now sleeping 10 seconds...");
                Thread.sleep(10000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < 10; i++) {
            executor.execute(thread);
        }
    }
}

 

 

corePoolSize와 maxPoolsize, queueCapacity와의 관계


결과를 보면 queue의 사이즈만큼 내부의 queue에 1씩 증가하는 것을 알 수 있습니다. 그리고 queue안에서 max까지 증가했을 때 thread가 하나 더 active 되는 것을 확인할 수 있습니다. 따라서 core에서 max로의 thread 수의 증가는 queue의 사이즈가 가득 차게되면 추가로 thread를 생성하여 사용한다는 것을 알 수 있습니다.

@Slf4j
public class Example {
    public static void main(String[] args) {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setCorePoolSize(1);
        executor.setQueueCapacity(5);
        executor.setMaxPoolSize(5);
        executor.setThreadNamePrefix("async-");
        executor.initialize();

        Runnable thread = () -> {
            try {
                log.info(Thread.currentThread().getName() + ", Now sleeping 10 seconds...");
                Thread.sleep(10000);
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < 10; i++) {
            executor.execute(thread);
            log.info("poolSize : " + executor.getPoolSize() +
                     ", active : " + executor.getActiveCount() +
                     ", queue : " + executor.getThreadPoolExecutor().getQueue().size());
        }
    }
}

 

 

이메일 발송을 비동기로 실행


https://kdg-is.tistory.com/316

 

Spring boot - Thymeleaf 템플릿 메일발송

라이브러리 추가 implementation "org.springframework.boot:spring-boot-starter-mail" implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' application.yml 설정 구글 2차 비밀번호 발급..

kdg-is.tistory.com

 

AsyncConfig 클래스 작성
@Configuration
@EnableAsync
public class AsyncConfig extends AsyncConfigurerSupport {

    @Override
    public Executor getAsyncExecutor() {
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setThreadNamePrefix("async-");
        executor.setCorePoolSize(5);
        executor.setMaxPoolSize(10);
        executor.setQueueCapacity(100);
        executor.setWaitForTasksToCompleteOnShutdown(true);
        executor.setAwaitTerminationSeconds(60);
        executor.initialize();
        return executor;
    }
}

 

기존 메서드에 @Async 어노테이션 선언
@Component
@RequiredArgsConstructor
public class MailHandler {

    private final JavaMailSender javaMailSender;
    private final SpringTemplateEngine templateEngine;

    /**
     * 이메일 발송 함수
     * @param title 이메일 제목
     * @param to    받는 사람
     * @param templateName 템플릿 이름
     * @param values    이메일에 들어갈 값
     * @throws MessagingException
     * @throws IOException
     */
    @Async
    public void send(String title, String to, String templateName, HashMap<String, String> values) throws MessagingException, IOException {
        MimeMessage message = javaMailSender.createMimeMessage();
        MimeMessageHelper helper = new MimeMessageHelper(message, true);

        helper.setSubject(title); // 제목 설정
        helper.setTo(to); // 수신자 설정

        Context context = new Context();
        values.forEach((key, value) -> {
            context.setVariable(key, value);
        });

        String html = templateEngine.process(templateName, context);
        helper.setText(html, true );
        javaMailSender.send(message);
    }
}

 

실행 시간 비교

5.54 ms => 11 ms 줄어든 것을 알 수 있습니다.

 

 

ThreadPoolTaskExecutor 예외 처리 방법

https://kdg-is.tistory.com/332

 

Spring boot - ThreadPoolTaskExecutor Exception Handling

💡상황 ThreadPoolTaskExecutor를 사용하여 이용가능한 Thread의 숫자가 100개였지만 트레픽이 몰려 101개의 요청이 들어온 상황으로 가정하였습니다. ThreadPoolTaskExecutor 설정 setMaxPoolSize는 90으로 설정..

kdg-is.tistory.com

 

 

728x90
반응형