본문 바로가기
프로그래밍/Java & Spring

ThreadPoolTaskScheduler를 이용하여 자바 스케줄러 구현 및 강제종료 구현

by 방구석개발자 2021. 8. 2.
반응형

안녕하세요. 방구석 개발자입니다.
현재 진행중인 프로젝트에서 원하는 시간대 실행되는 배치를 등록하고 실행하는 기능을 만들어야 돼서 ThreadPoolTaskScheduler를 이용하여 구현하였는데요.
ThreadPoolTaskScheduler가 뭔지 또 어떻게 사용되는지 알아보도록 하겠습니다.

ThreadPoolTaskScheduler란

ThreadPoolTaskScheduler는 태스크실행 및 스케줄링에 사용되는 스프링 라이브러리 입니다.
배치 관리 할때 Thread를 개발자가 직접 제어하지 않고 실행하고 싶은 Task와 시간을 Pool에 넣습니다.그러면 ThreadPoolTaskScheduler에서 제어하여 해당 시간에 맞춰서 Thread를 생성 및 실행 시키고 종료 까지 해줍니다.

스프링 공식 문서

사용방법

SpringMVC의 경우 설정xml 파일에 다음과 같이 넣습니다. id값은 원하는 값으로 넣어주셔도 됩니다.

<task:scheduler id="jobScheduler" pool-size="10"/>

클래스 파일에 ThreadPoolTaskScheduler를 @Autowired 로 지정하여 변수를 만듭니다.

@Autowired private ThreadPoolTaskScheduler scheduler; 
private Map<String, ScheduledFuture<?>> jobMap=new HashMap<>();


다음 메서드 처럼 스케줄 등록및 실행 , 중지를 제어합니다.

public void exampleMethod(){
  scheduler.setPoolSize(10); //스레드풀의 사이즈를 결정합니다. //스케줄러에 실행하고 싶은 Task 넣기 
  RunnableEx runnableEx=new RunnableEx();
  ScheduledFuture<?> scheduledFuture=scheduler.schedule(runnableEx,new CronTrigger("0 0 12 * *"));//매일 12시(정오)에 실행되는 Task를 스케줄에 등록함. 
  jobMap.put("testId",scheduledFuture); 
  scheduler.shutdown(); //작업중인 스케줄러를 중지합니다.
  jobMap.remove("testId"); 
}

Trigger는 Cron과 Periodic이 있어서 각각 편한쪽으로 설정하시면됩니다.

종료시 이슈 사항

이번 프로젝트에서 스케줄을 강제종료하는 기능 구현 중에 문제가 발생하여 오랜시간 애먹었습니다.
Thread는  destroy() , stop() 메소드 대신 interrupt를 일으켜 사용중지 할것을 권하고 있습니다.

자바에서 stop 메소드는 안전하지 않고 interrupt메소드 사용을 권장합니다.

그래서 ThreadPoolTaskScheduler의 shutdown() 메소드를 내부적으로 살펴보았는데요.
interruptIdleWorkers 메소드를 호출하고 거기서 Thread.interrupt()를 호출합니다. 하여 InterruptedException 처리를 해줘야 강제종료를 구현할수 있습니다.

 class RunnableEx implements Runnable {
     @Override public void run() {
         try{
          System.out.println("배치 시작");
          serviceLogic();//실행하고 싶은 서비스 로직 구현
          System.out.println("배치 종료");
         }catch (InterruptedException e){
          //배치가 강제종료되면 여기로 넘어오게됩니다. e.printStackTrace(); 
         }finally {
          //do finally Logic 
         } 
     } 
 }

그러나 여기에는 또 다른 함정이 있습니다. servicLogic() 부분에서 오랜 시간이 걸려 강제종료하면 종료가 되지 않았습니다. 그래서 ThreadPoolTaskScheduler의 shutdown()메소드를 살펴보았습니다.
shutdown() 메소드는 부모클래스 ExecutorConfigurationSupport 에 정의 되어 있습니다.ExecutorConfigurationSupport 의 shutdown()메소드를 따라가 보겠습니다.

See Also: ExecutorService.shutdown(), ExecutorService.shutdownNow()

ExecutorService.shutdown() 을 사용하여서 ExecutorService.shutdown()을 따라가서 주석을 보았습니다.

There are no guarantees beyond best-effort attempts to stop processing actively executing tasks. For example, typical
implementations will cancel vi
a Thread.interrupt,
so any task that fails to respond to interrupts may never terminate.
능동적으로 실행 중인 작업의 처리를 중지하려는 최선의 시도 외에는 보장이 없습니다. 예를 들어 일반적인 구현은 Thread.interrupt를 통해 취소되므로 인터럽트에 응답하지 않는 작업은 절대 종료되지 않을 수 있습니다.

이 뜻은 InterruptedException처리를 하지 않으면 작업 종료가 안될수 있다는 뜻입니다.
예를 들어서 service로직에 InterruptedException 처리 하지 않은 채로 속도가 느린 쿼리를 여러번 실행 해야하고 있으면 db를 다 처리 하기 전까지 종료가 되지 않습니다.

 public void serviceLogic() {
        //do service
        for(int i=0;i<10;i++){
            selectDb();
            updateDb();
            insertLog();
        }// 강제종료가 되지 않고 계속 실행됨
    }

    public void updateDb(){
        //updateDB
    }

    public void selectDb(){
        //selectDB
    }

    public void insertLog(){
        //insertDB
    }

해당부분은 강제종료시에 boolean값으로 트리거 시켜서 InterruptedException을 던져주어 해결하였습니다.

 private boolean shutDown=false;
 public void isStop(){
       scheduler.shutdown(); //작업중인 스케줄러를 중지합니다.
       shutDown=true; // InterruptedException를 일으키는 boolean
 } 
 
  public void serviceLogic() throws InterruptedException {
        //do service
        for(int i=0;i<10;i++){
            if(shutDown) throw new InterruptedException();
            selectDb();
            updateDb();
            insertLog();
        }
    }

    public void updateDb(){
        //updateDB
    }

    public void selectDb(){
        //selectDB
    }

    public void insertLog(){
        //insertDB
    }

정리

ThreadPoolTashkScheduler 를 종료시에는 서비스로직에서 InterruptedException처리를 해주어야 종료될수 있습니다.
회사에서 며칠을 고생해서 구현 한거라서 정리해보았습니다.
최대한 잘 설명을 하려고 하였으나 두서없이 적은거 같습니다.. ㅜㅜ
다음에 다시 한 번 정리해보도록 하겠습니다.
감사합니다.

반응형

댓글