Taskscheduler의 동작 원리

  • TaskScheduler는 PriorityQueue 기반 DelayedWorkQueue에 예약 작업을 쌓는다.
  • 시간순으로 꺼내서 스레드 풀로 병렬 실행한다.

내부 클래스 동작 설명

ThreadPoolTaskScheduler

  • ScheduledExecutorService 타입 필드를 가짐.
  • 기본적으로 ScheduledThreadPoolExecutor를 인스턴스화해서 내부적으로 사용.

ScheduledThreadPoolExecutor

  • 실제로 Runnable Task를 관리하고 실행.
  • 내부적으로 DelayedWorkQueue를 사용

DelayedWorkQueue

  • ScheduledFutureTask들을 PriorityQueue에 담아서 실행 예약.

ScheduledFutureTask

  • 각 작업이 어떤 시간에 실행될지 나타내는 Future.
  • 시간 단위로 우선순위 정렬

Taskscheduler를 Spring에 적용

사용 중인 Thread 출력

현재 JVM 프로세스에서 생성되어 살아있는 모든 스레드의 개수를 출력해준다.

log.info("[Thread Size] {}", Thread.getAllStackTraces().size());

Taskscheduler의 스레드 크기

Config 설정

  • 사용할 쓰레드의 크기를 정할 수 있다.(default: 1)
  • 쓰레드 이름을 prefix로 설정해줄 수 있다.
@Bean  
public ThreadPoolTaskScheduler taskScheduler() {  
    ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();  
    scheduler.setPoolSize(2);  
    scheduler.setThreadNamePrefix("TASK-SCHEDULER-");  
    scheduler.initialize();  
    return scheduler;  
}

시작

  • 0부터 10까지 순차적으로 출력을 진행하는 함수를 넣어준다.
  • 실행 순서는 10부터 실행되도록 설정한다.
  • 실행 했을 때 Thread의 크기를 비교한다.
logThreadSize();  
for (int i = 0; i < 10; i++) {  
	final int inner = i;  
	Runnable task = () -> log.info("안녕하세요 : {}", inner);  
	taskScheduler.schedule(task, Instant.now().plusSeconds(15 - i));  
	log.info("[태스크 삽입] {}", inner);  
}  
logThreadSize();

출력 로그

  • 입력된 순서와 상관없이 예약된 시간 순서대로 메소드가 실행되는 것을 확인할 수 있다.
  • Thread의 크기는 미리 설정한 Thread의 크기만큼 할당된다.
  • 실행 thread의 이름도 설정한 것으로 확인이 가능하다.
[line-demo] [           main]  : [Thread Size] 25
[line-demo] [           main]  : [태스크 삽입] 0
[line-demo] [           main]  : [태스크 삽입] 1
...
[line-demo] [           main]  : [태스크 삽입] 8
[line-demo] [           main]  : [태스크 삽입] 9
[line-demo] [           main]  : [Thread Size] 27
[line-demo] [ASK-SCHEDULER-2]  : 안녕하세요 : 9
[line-demo] [ASK-SCHEDULER-1]  : 안녕하세요 : 8
...
[line-demo] [ASK-SCHEDULER-2]  : 안녕하세요 : 1
[line-demo] [ASK-SCHEDULER-1]  : 안녕하세요 : 0

추가로 도움되는 method

시간 계산하는 method

Duration.between 메소드를 사용해서 두 시간 사이의 를 계산한다.

private long calculateDelaySeconds(LocalDateTime now, LocalDateTime targetDateTime) {  
    Duration duration = Duration.between(now, targetDateTime);  
    return Math.max(NO_DELAY, duration.getSeconds());  
}

현재 사용되는 쓰레드 개수

private void logThreadSize() {  
    log.info("[Thread Size] {}", Thread.getAllStackTraces().size());  
}

참고 블로그

TaskScheduler를 사용해서 동적으로 작업 예약