저작권 안내: 저작권자표시 Yes 상업적이용 No 컨텐츠변경 No

스프링5 입문

JSP 2.3

JPA 입문

DDD Start

인프런 객체 지향 입문 강의
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 이전에는 이러한 기능을 언어 차원에서 지원하지 않았기 때문에 필요할 경우 개발자가 직접 구현을 해 주어야 했었지만, 이제는 매우 손쉽게 구현할 수 있게 되었다. 이제 개발자들은 이 두 클래스를 사용하여 좀더 간단하고 좀더 빠르게 시간과 관련해서 스케쥴링이 필요한 기능을 구현할 수 있을 것이다.

Posted by 최범균 madvirus

댓글을 달아 주세요

  1. hoolala 2009.04.02 22:17 신고  댓글주소  수정/삭제  댓글쓰기

    자바를 공부하고 있는 학생입니다. 좋은 강좌에 먼저 감사드립니다.

    그런데 질문이 있는데요..
    만약 TimerTask 클래스를 extends 하지 않고 단지 Runnable 만 implements 하여 클래스 이름을 TimerTask 로 하게 되면 어떻게 되나요.

    실제 해본 결과 Timer 객체의 schedule(TimerTask task,Date time) 이 메소드에서는 제가 만든 클래스의 인스턴스를 거부하더군요..

    The method schedule(TimerTask, Date) in the type Timer is not applicable for the arguments (TimerTask, Date) 라는 에러가 뜨던데요..

    왜 이런 에러가 뜨는 건지 궁금합니다!! ㅇㅅㅇ!!

    (아 물론 제가 만든 TimerTask 클래스가 같은 디렉토리에 있었기 때문에 import java.util.TimerTask 는 생략했습니다)

  2. 나그네 2018.07.10 14:57 신고  댓글주소  수정/삭제  댓글쓰기

    정보 많이 참고했습니다 ^^ 감사합니다.

  3. 음... 2018.07.13 18:16 신고  댓글주소  수정/삭제  댓글쓰기

    scheduleAtFiexedRate는 위와 조금 설명과 다를 수 있습니다.
    타이머는 여러 task를 수행할 수 있는데 단일 쓰레드이기 때문에 선행 작업이 이후 작업에 영향을 줄 수 있습니다. 다만 위의 메쏘드를 쓰게 되면 지연된 시간 동안 수행되지 못한 만큼의 작업을 한번에 수행한다는 의미가 있는 것 같습니다. 실제로 테스트 해보니 그러네요.. 고정 주기가 아니라 고정 비율이라는 건 그런 의미에서 사용된 것 같습니다.

    • 최범균 madvirus 2018.07.16 10:58 신고  댓글주소  수정/삭제

      네 Timer는 단일 쓰레드이기 때문에 선행 작업이 시간이 오래 걸리면 말씀하신 것처럼 후속 작업 실행 시간에 영향을 줍니다.
      작업을 큐에 보관했다가 지연된 작업까지 한 번에 실행하는 거죠.
      그래서 TimerTask 실행 시간이 지정한 간격을 초과할 가능성이 있거나 여러 TimerTask를 한 Timer로 실행한다면, TimerTask는 실제 작업을 비동기로 실행하고 Timer를 일종의 트리거로만 사용하는 것이 좋을 것 같습니다.