본문 바로가기
블로그 이미지

방문해 주셔서 감사합니다! 항상 행복하세요!

  
   - 문의사항은 메일 또는 댓글로 언제든 연락주세요.
   - "해줘","답 내놔" 같은 질문은 답변드리지 않습니다.
   - 메일주소 : lts06069@naver.com


Java(자바)

Java quartz synchronize (quartz 동기화, quartz 순서)

야근없는 행복한 삶을 위해 ~
by 마샤와 곰 2020. 5. 26.

자바 쿼즈(quartz) 라이브러리를 사용하면 cron 형식의 스케줄링을 손 쉽게 구현 할 수 있습니다.

초, 분, 시간단위 및 기타 여러조건에 따라서 원하는 기능을 만들 수 있습니다.

이러한 쿼즈에서 간간히 만나는 문제가 "작업중인 내용이 설정한 간격보다 길어지는 경우" 입니다.

 

예를 들어, 3초 간격으로 동작해야되는 작업이 존재한다고 가정하여 봅니다.

해당작업이 3초 이내에 끝나면 사실 아무런 문제가 없습니다.

그런데 해당 작업이 3초가 넘어서 끝나게되는 경우, 설정한 스케줄 간격 3초에 의해서 작업종료여부와 상관 없이 새로운 작업이 수행됩니다.

이러한 경우 2번동작 하므로 원하지 않는 결과를 만들어 낼 수 있습니다. (물론 그 이상이 될 수 있습니다.)

 

기존 작업이 끝나지 않았음에도 불구하고 시간설정에 의해서 새로운 작업이 오버랩(overlapping) 되는 현상을 방지하기위해서는 다양한 방법이 존재 합니다.   * 동시성 문제 대비(race conditions)

 

테스트 환경을 구성하기 위해 쿼즈 라이브러리를 추가하여 봅니다.

쿼즈(quartz)라이브러리는 메이븐 또는 그레이들로 쉽게 설치 할 수 있습니다.

<dependency>
    <groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.3.0</version>
</dependency>    

 

다음으로 작업을 수행할 Job 클래스를 만들어 줍니다.

해당 클래스에는 작업을 관리하기 위해서 synsynchronized를 사용하였습니다.

import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

public class MyWorker implements Job{

  private static boolean isWorking = false;
  
  public void execute(JobExecutionContext arg0) throws JobExecutionException {
    todo();
  }
  
  private void todo(){
    synchronized (this) {
      if(!isWorking){
        isWorking = true;
        for(int i = 0;i < 10; i++){
          try {
            Thread.sleep(300);
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
        System.out.println("[***" + Thread.currentThread().getName() + "***]");
        isWorking = false;
      }
    } 
  }
    
}

 

해당 클래스를 동작시키는 메인메소드 입니다.

import static org.quartz.CronScheduleBuilder.cronSchedule;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;

import org.quartz.CronTrigger;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SchedulerFactory;
import org.quartz.impl.StdSchedulerFactory;

public class App {
  private static final SchedulerFactory SF = new StdSchedulerFactory();
  private static Scheduler sched = null;
  private static JobDetail job = null;
  private static CronTrigger trigger = null;  
  
  public static void main( String[] args ){
    System.out.println( "Schedule start" );
    String cronSch = "0/1 * * * * ?";
    try {
      sched = SF.getScheduler();
      job = newJob(MyWorker.class).withIdentity("MyWorker", "jobGroup").build();
      trigger = newTrigger() .withIdentity("MyWorker", "jobGroup").withSchedule(cronSchedule(cronSch)) .build();
      sched.scheduleJob(job, trigger);      
      sched.start();  
    } catch (Exception e) {
      e.printStackTrace();
      if(sched != null){
        try {
          sched.shutdown();
        } catch (SchedulerException e1) {
          e1.printStackTrace();
        }
      }
      sched = null;
    }
  }
}

 

스케줄 시간은 1초입니다.

1초동안 작업이 계속해서 실행 되지만, synchronized와 static 변수인 isWorking에 의해서 실제로 작업은 1번만 동작하게 하였습니다.

스케줄이 겹치지 않는 것을 볼 수 있습니다.

 

synsynchronized를 세마포어나 뮤텍스로 바꾸어 적용하여도 올바르게 동작 합니다.

작업이 겹치지 않고 동기화 구간에 의해서 1번만 동작하게 하였습니다.

그런데 해당방법은 동작하는 스케줄러의 순서가 보장되지 않습니다.

스케줄러의 순서가 보장되게 하려면 아주 간단한 에노테이션을 붙여주면 쉽게 할 수 있습니다.

Job역할을 하는 클래스의 내용을 수정하여 보겠습니다.

import org.quartz.DisallowConcurrentExecution;
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;

@DisallowConcurrentExecution 
public class MyWorker implements Job{

  public void execute(JobExecutionContext arg0) throws JobExecutionException {
    todo();
  }
  
  private void todo(){
    for(int i = 0;i < 10; i++){
      try {
        Thread.sleep(300);
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
    }
    System.out.println("[***" + Thread.currentThread().getName() + "***]");
  }
    
}

 

기존 동기화에 사용되었던 방법을 제거하고 DisallowConcurrentExecution 라는 에노테이션을 설정 하였습니다.

해당 에노테이션은 작업이 끝나지 않는 Job 클래스에 대해서, 설정한 시간에 의해서 작업이 다시 동작해야 되더라도 겹치지 않게 해 주는 역할을 합니다.

작업이 겹치지도 않으면서 순서도 보장이 됩니다.

 

조금 더 간결하면서 서로 겹치지 않도록 하였습니다.

쿼즈(quartz)에서 스케줄러의 동작을 겹치지 않도록 하기 위해서는 DisallowConcurrentExecution 에노테이션을 활용하는 것이 조금 더 편리합니다.

그러나, 세부적인 조정이나 관리가 필요한 경우에는 synchronized나 세마포어, 뮤텍스등을 활용해야 될 것 입니다.

이상으로 쿼즈(quartz) 순서보장에 대한 내용에 대해 살펴 보았습니다.

 

반응형
* 위 에니메이션은 Html의 캔버스(canvas)기반으로 동작하는 기능 입니다. Html 캔버스 튜토리얼 도 한번 살펴보세요~ :)
* 직접 만든 Html 캔버스 애니메이션 도 한번 살펴보세요~ :)

댓글