주요글: 도커 시작하기
반응형
JDK1.3에 새롭게 추가된 java.util.Timer 클래스를 사용하여 이벤트의 실행을 제어하는 것에 대해서 알아본다.

java.util.Timer 클래스와 java.util.TimerTask 클래스

유닉스나 리눅스에서 특정 시간에 어떤 프로세스를 실행시키고자 할 경우에 많이 사용되는 것이 cron 명령어와 at 명령어이다. 이 두 명령어는 임시 파일을 주기적으로 삭제하거나 중요 데이터를 일정 주기로 백업하고자 할 때 많이 사용된다. 윈도우 2000 서버 역시 이와 비슷한 기능을 제공하고 있다. 하지만, 아쉽게도 JDK1.2 까지는 유닉스나 리눅스의 cron 이나 at 명령어와 비슷한 기능을 수행하기 위해서는 개발자가 직접 쓰레드를 이용하여 구현해주어야 했다.

하지만, JDK1.3에 새롭게 추가된 클래스인 java.util.Timer 클래스와 java.util.TimerTask 클래스를 사용하면 이런 기능을 매우 손쉽게 구현할 수 있게 된다. 이 글에서는 Timer 클래스와 TimerTask 클래스에 대해서 알아보고, 어떻게 사용될 수 있는 지 예를 통해 살펴보자.

java.util.Timer 클래스는 백그라운드에서 특정한 시간 또는 일정 시간을 주기로 반복적으로 특정 작업을 실행할 수 있도록 해 준다. Timer 클래스는 크게 다음과 같이 세 가지 종류의 메소드를 제공한다.

  • schedule
  • scheduleAtFixedRate
  • cancel
위에서 schedule() 메소드는 특정한 시간에 원하는 작업을 수행하고자 할 때 사용하는 메소드로 다음 표와 같이 4가지 형태가 존재한다.

void schedule(TimerTask task, Date time)
지정한 시간(time)에 지정한 작업(task)을 수행한다.
void schedule(TimerTask task, Date firstTime, long period)
지정한 시간(firstTime) 부터 일정 간격(period)으로 지정한 작업(task)을 수행한다.
void schedule(TimerTask task, long delay)
일정 시간(delay)이 지난 후에 지정한 작업(task)을 수행한다.
void schedule(TimerTask task, long delay, long period)
일정 시간(delay)이 지난 후에 일정 간격(period)으로 지정한 작업(task)을 수행한다.
schedule() 메소드 중에는 지정한 시간부터 작업을 수행하는 것이 두 개 존재한다. 이 두 메소드는 지정한 시간이 현재 시간보다 과거일 경우 바로 TimerTask 작업을 수행한다. 그리고 schedule() 메소드에서 일정 간격으로 작업을 수행하는 메소드가 있는데, 이들 메소드는 fixed-delay 방식으로 작업을 진행한다. fixed-delay 방식은 만약 선행 작업이 가비지 콜렉션이나 기타 다른 이유 때문에 지연될 경우 그 다음에 수행되는 작업의 시작이 그 시간만큼 지연된다. 따라서, schedule() 메소드는 일정 주기로 실행되는 작업들이 정확한 시간에 실행될 필요가 없는 경우에 알맞다.

정확하게 일정 시간 간격으로 작업을 실행하기 위해서는 scheduleAtFixedRate() 메소드를 사용해야 한다. scheduleAtFixedRate() 메소드는 fixed-rate 방식으로 작업을 진행한다. fixed-rate 방식은 선행 작업이 지연되는 여부에 상관없이 지정된 시간에 작업을 실행한다. 따라서, 정확하게 특정 시간 마다 실행해야 하는 작업의 경우에는 schedule() 메소드가 아닌 scheduleAtFixedRate() 메소드를 사용하는 것이 좋다. 다음 표는 scheduleAtFixedRate() 메소드를 정리한 것이다.

void scheduleAtFixedRate(TimerTask task, Date firstTime, long period)
지정한 시간(firstTime)부터 일정 간격(period)으로 지정한 작업(task)을 수행한다.
void scheduleAtFixedRate(TimerTask task, long delay, long period)
일정한 시간(delay)이 지난후에 일정 간격(period)으로 지정한 작업(task)을 수행한다.
Timer 클래스는 스케쥴을 중지할 수 있도록 cancel() 메소드를 제공한다. cancel() 메소드는 Timer를 중지시키며, 실행될 예정인 모든 작업을 취소한다. 하지만, 현재 실행되고 있는 TimerTask 작업에 대해서는 영향을 미치지는 않는다.

이제 TimerTask 클래스에 대해서 알아보자. TimerTask 클래스는 Timer 클래스가 수행할 작업을 나타낸다. TimerTask 클래스는 Runnable 인터페이스를 implements 하고 있으며, 그 외에 몇 가지 필요한 메소드를 정의하고 있다. 다음 표는 TimerTask 클래스가 제공하고 있는 메소드를 정리한 것이다.

boolean cancel()
이 TimerTask 작업을 취소한다.
abstract void run()
이 TimerTask가 실행할 작업
long scheduledExecutionTime()
가장 최근에 이 작업이 실행된 시간을 리턴한다.
TimerTask 클래스를 보면 추상 메소드인 run()이 있는데 개발자는 TimerTask 클래스를 상속받은 후에 run() 메소드를 알맞게 구현해주면 된다.

간단한 예제

간단하게 Timer 클래스를 사용하여 3초마다 문자열을 현재 시간을 출력해주는 프로그램을 작성해보자. 이 프로그램은 다음과 같다.

import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;

public class PrintTimer {
   
   public static void main(String[] args) {
      ScheduledJob job = new ScheduledJob();
      Timer jobScheduler = new Timer();
      jobScheduler.scheduleAtFixedRate(job, 1000, 3000);
      try {
         Thread.sleep(20000);
      } catch(InterruptedException ex) {
         //
      }
      jobScheduler.cancel();
   }
}

class ScheduledJob extends TimerTask {
   
   public void run() {
      System.out.println(new Date());
   }
}

PrintTimer는 main() 메소드에서 Timer 객체를 생성한 후, scheduleAtFixedRate() 메소드를 사용하여 1초 후부터 3초 간격으로 ScheduledJob 클래스의 run() 메소드를 실행한다. PrintTimer 클래스를 실행해보면 다음과 같은 결과가 출력될 것이다.

Fri Apr 13 15:26:47 GMT+09:00 2001
Fri Apr 13 15:26:50 GMT+09:00 2001
Fri Apr 13 15:26:53 GMT+09:00 2001
Fri Apr 13 15:26:56 GMT+09:00 2001
Fri Apr 13 15:26:59 GMT+09:00 2001
...

Daemon Thread?

Timer 클래스의 기본 생성자(즉, 파라미터가 없는 생성자)를 사용하여 Timer 클래스를 생성할 경우 Timer 클래스와 관련된 작업을 실행할 때 사용되는 쓰레드는 데몬 쓰레드로 실행되지 않는다. 데몬 쓰레드로 실행되지 않는다는 것은 Timer 객체와 관련된 클래스가 종료되지 않는 한 어플리케이션의 실행이 끝나지 않는다는 것을 의미한다. 따라서 앞에 예제에서도 cancel() 메소드를 사용하여 강제적으로 Timer 클래스와 관련된 쓰레드의 실행을 종료했었다. 실제로 앞의 PrintTimer 예제에서 jobScheduler.cancel() 부분을 삭제한다면 어플리케이션은 종료하지 않고 계속해서 실행될 것이다.

하지만, 때때로 Timer 클래스와 관련된 쓰레드를 데몬 쓰레드로 실행하고 싶은 경우가 있다. 이런 경우를 위해 Timer 클래스는 데몬 쓰레드로 지정할 지의 여부를 입력받을 수 있는 생성자를 제공하고 있다. 이 생성자는 boolean 값을 갖는 한 개의 파라미터를 입력받는다. 이 때, boolean 값이 true이면 Timer 클래스와 관련된 쓰레드들은 데몬 쓰레드로 설정된다. 예를 들어, 다음의 NewPrintTimer 클래스를 살펴보자.

import java.util.Timer;
import java.util.TimerTask;
import java.util.Date;

public class NewPrintTimer {
   
   public static void main(String[] args) {
      NewScheduledJob job = new NewScheduledJob();
      Timer jobScheduler = new Timer(true);
      jobScheduler.scheduleAtFixedRate(job, 1000, 3000);
      try {
         Thread.sleep(20000);
      } catch(InterruptedException ex) {
         //
      }
      // cancel() 메소드를 호출하지 않는다.
   }
}

class NewScheduledJob extends TimerTask {
   
   public void run() {
      System.out.println(new Date());
   }
}

NewPrintTimer 클래스의 mani() 메소드를 살펴보면 Timer 클래스를 생성할 때 Timer(true) 생성자를 사용하여 Timer 객체와 관련된 쓰레드를 데몬으로 설정하고 있으며, Timer 클래스의 cancel() 메소드를 호출하지 않고 있다. main() 메소드의 실행이 끝나게 되면 Timer 클래스와 관련된 쓰레드는 모두 데몬 쓰레드이기 때문에 어플리케이션은 종료하게 된다. 실제로 NewPrintTimer를 실행해보면 이 사실을 확인할 수 있다.

결론

java.util.Timer 클래스와 java.util.TimerTask 클래스를 사용하여 반복적으로 또는 특정한 시간에 원하는 작업을 실행할 수 있게 되었다. JDK1.3 이전에는 이러한 기능을 언어 차원에서 지원하지 않았기 때문에 필요할 경우 개발자가 직접 구현을 해 주어야 했었지만, 이제는 매우 손쉽게 구현할 수 있게 되었다. 이제 개발자들은 이 두 클래스를 사용하여 좀더 간단하고 좀더 빠르게 시간과 관련해서 스케쥴링이 필요한 기능을 구현할 수 있을 것이다.

+ Recent posts