>  기사  >  Java  >  Java 예약 작업: Java Timer 클래스를 사용하여 타이밍 작업 기능을 구현합니다.

Java 예약 작업: Java Timer 클래스를 사용하여 타이밍 작업 기능을 구현합니다.

高洛峰
高洛峰원래의
2017-01-11 16:07:533618검색

1. 개요

Java에서는 타이밍 작업 실행 기능을 구현하기 위해 주로 Timer 클래스와 TimerTask 클래스를 사용합니다. 그 중 Timer는 지정된 계획에 따라 백그라운드 스레드에서 지정된 작업을 실행하는 데 사용됩니다.

TimerTask는 추상 클래스이며, 그 하위 클래스는 Timer에 의해 예약될 수 있는 작업을 나타냅니다. 실행할 특정 코드는 TimerTask에서 구현해야 하는 run 메서드에 작성됩니다.

2. 가장 간단한 예를 먼저 살펴보겠습니다

코드로 설명합니다

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    startTimer();
    Thread.sleep(1000*5); //休眠5秒
    System.out.println("main  end:"+getCurrentTime());
  }
 
  public static void startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task  run:"+getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 0);
  }
}

인쇄를 통해 정보를 쉽게 관찰할 수 있도록 다음과 같이 몇 가지를 추가했습니다. 메인 메소드 정보를 인쇄하고 Thread.sleep을 호출하여 메인 스레드를 잠시 동안 휴면 상태로 만듭니다. 또한 현재 날짜를 가져오기 위한 getCurrentTime 메서드가 클래스에 추가됩니다.

위 코드는 startTimer 메소드에서 먼저 TimerTask 객체(타이머가 실행할 작업)를 생성한 후 Timer 객체를 생성한 후 Timer 클래스의 Schedule 메소드를 호출합니다. Timer 클래스에는 다양한 매개 변수를 가진 여러 예약 메서드가 있습니다. 여기서 사용되는 내용은 다음과 같습니다.

public void schedule(TimerTask task, long delay)

이 방법의 의미는 타이머가 작업을 실행하기 전에 지연(밀리초)을 지연한다는 것입니다. 지연이 음수 또는 0이면 작업이 즉시 실행됩니다. 그리고 일회성 실행 작업이므로 해당 작업은 향후 반복적으로(또는 예약) 실행되지 않습니다.

Timer 클래스의 경우에도 동일한 기능을 갖는 메소드가 다음과 같이 제공됩니다.

public void schedule(TimerTask task, Date time)

이 메소드와 위 메소드의 차이점은 위 메소드에서 지연 시간을 지정한다는 것입니다. 이 방법은 특정 시점에 실행을 지정하는 것입니다. 시스템의 현재 시간이 매개변수 시간에 지정된 시간을 초과한 경우 작업이 즉시 실행됩니다.

위 코드를 실행하면 프로그램이 즉시 다음과 유사한 2개의 메시지를 인쇄하는 것을 발견했습니다.

main start:2016-01-13 22:23:18
task run:2016-01-13 22:23:18

여기서 스케줄 메소드에 전달하는 지연 매개변수 값이 0이므로 작업이 즉시 실행되므로 두 문으로 시간이 인쇄됩니다. 동일합니다. 이것이 있어야 합니다. 수신 지연 값을 직접 변경하여 출력 정보의 변경 사항을 확인할 수 있습니다. 약 5초(즉, 수면 시간) 후에 또 다른 메시지가 인쇄되었습니다.

main end:2016-01-13 22:23:23

메시지를 인쇄하는 시간은 다음과 같습니다. 위 진술의 차이는 5초인데, 이는 수면 설정과 일치하며 매우 합리적입니다.

그러나 매우 흥미로운 현상을 발견하게 됩니다. 이때 프로세스가 종료되지 않습니다. 이는 타이머가 완료되면 프로세스가 종료되지 않음을 나타냅니다. 나중에 실행 대기 중인 작업이 있으면 타이머에 생성된 백그라운드 스레드가 즉시 종료되지 않습니다. 관련 Java 문서를 확인해보니 타이머 스레드가 적극적으로 종료되지 않고 가비지 수집을 기다려야 한다고 설명했는데, Java의 가비지 수집 대기는 코드 자체로는 제어할 수 없고 가상 머신에 의해 제어됩니다.

조사한 결과 Timer 객체를 생성하고 Timer 타이머 = new Timer() 문을 실행하면 타이머 스레드가 생성된다는 사실을 발견했습니다. 즉, 위 코드에 타이머.schedule(task, 0); 문이 포함되어 있지 않더라도 프로그램은 종료되지 않습니다. 이것은 상당히 불합리하다고 느껴집니다. Timer 클래스의 소스 코드를 다시 연구한 결과 부울 매개변수가 있는 생성자가 있음을 발견했습니다.

public Timer(boolean isDaemon)

매개변수 이름에서 볼 수 있듯이 매개변수 값이 true이면 Timer 생성됨 타이머 스레드는 데몬 스레드입니다. 데몬 스레드의 의미는 Java 프로세스의 모든 작업 스레드가 종료되면 데몬 스레드가 자동으로 종료된다는 것입니다.

이제 위 예제에서 Timer 개체를 생성하는 코드를 다음과 같이 변경하면 됩니다. Timer 타이머 = new Timer(true);

프로그램이 실행 중임을 확인한 후 , 메인 스레드를 기다립니다(메인 스레드는 작업 스레드인 데몬 스레드가 종료된 후 프로그램이 종료됩니다. 이는 타이머 스레드도 종료됨을 의미합니다. 이는 true 매개변수를 추가한 후 생성된 스레드는 데몬 스레드입니다.

그런데 문제는 실제 애플리케이션 시나리오에서는 실행 중인 작업자 스레드가 많아 프로그램이 자연스럽게 종료되지 않는다는 것입니다. 그렇다면 타이머를 즉시 종료하거나 닫으려면 어떻게 해야 할까요? 이에 대해서는 아래에서 소개하겠습니다.

3. 타이머 종료

Timer 클래스는 타이머를 취소하는 취소 메소드를 제공합니다. 취소 메소드를 호출하면 이 타이머가 종료되고 현재 예약된 모든 작업이 삭제됩니다. 현재 실행 중인 작업(있는 경우)을 방해하지 않습니다. 타이머가 종료되면 해당 실행 스레드도 종료되며 타이머에 대해 더 이상 작업을 예약할 수 없습니다.

이 타이머에 의해 호출된 타이머 작업의 실행 메서드 내에서 이 메서드를 호출하면 실행 중인 작업이 이 타이머에 의해 실행된 마지막 작업인지 확실히 확인할 수 있습니다. 이 메서드는 반복해서 호출할 수 있지만 두 번째 이후 호출은 효과가 없습니다.

다른 예제 코드를 살펴보겠습니다.

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    Timer timer = startTimer();
    Thread.sleep(1000*5); //休眠5秒
    System.out.println("main  end:"+getCurrentTime());
    timer.cancel();
  }
 
  public static Timer startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task  run:"+getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 0);
    return timer;
  }
}

프로그램을 실행하면 출력은 위의 예제와 정확히 동일합니다. 차이점은 main 메소드가 종료될 때입니다. 프로세스가 능동적으로 종료됩니다. 이는 타이머 스레드가 닫혔음을 의미합니다.

메인 메소드에서 취소 메소드를 호출했기 때문입니다. TimerTask의 run 메소드에서 cancel 메소드가 호출되지 않는 경우 실행하려는 작업이 시작되었거나 완료되었는지 확인하고, 그렇지 않으면 작업이 아직 시작되지 않은 경우에 유의하십시오. 취소를 호출하면 모든 작업이 실행되지 않습니다. 예를 들어 위의 코드

比如上面的代码,如果我们不在main方法中调用cancel方法,而是在startTimer方法中 timer.schedule(task, 0); 语句后加上timer.cancel();语句,运行后会发现,定时器任务不会被执行,因为还未来得及执行就被取消中止了。

四、定时执行任务

上面的例子,我们介绍的是一次性任务,也就是定时器时间到了,执行完任务,后面不会再重复执行。在实际的应用中,有很多场景需要定时重复的执行同一个任务。这也分两种情况,一是每隔一段时间就执行任务,二是每天(或每周、每月等)的固定某个(或某几个)时间点来执行任务。

我们先来看第一种情况,实现每隔10秒执行同一任务的例子。代码如下:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    startTimer();
  }
 
  public static void startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task  run:"+getCurrentTime());
        try {
          Thread.sleep(1000*3);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 1000*5,1000*10);
  }
}

执行上述程序,输出信息如下(因为定时器没有停止,重复执行任务,会不断输出,这里只拷贝了前面的一些输出)

main start:2016-01-14 08:41:14
task   run:2016-01-14 08:41:19
task   run:2016-01-14 08:41:29
task   run:2016-01-14 08:41:39
task   run:2016-01-14 08:41:49
task   run:2016-01-14 08:42:00
task   run:2016-01-14 08:42:10
task   run:2016-01-14 08:42:20
task   run:2016-01-14 08:42:30
task   run:2016-01-14 08:42:40

在上面的代码中,我们调用了 timer.schedule(task, 1000*5,1000*10); 这个含义是该任务延迟5秒后执行,然后会每隔10秒重复执行。我们观察输出信息中打印的时间,是与预期一样的。 另外可以看出,间隔是以任务开始执行时间为起点算的,也就是并不是任务执行完成后再等待10秒。

Timer类有两个方法可以实现这样的功能,如下:

public void schedule(TimerTask task, long delay, long period)
 
public void schedule(TimerTask task, Date firstTime, long period)

我们上面代码用的是第一个方法。两个方法区别在于第一次执行的时间,第一个方法是在指定延期一段时间(单位为毫秒)后执行;第二个方法是在指定的时间点执行。

这时我们考虑如下场景,如果某个任务的执行耗时超过了下次等待时间,会出现什么情况呢? 我们还是通过代码来看:

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:"+getCurrentTime());
    startTimer();
  }
 
  public static void startTimer(){
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task begin:"+getCurrentTime());
        try {
          Thread.sleep(1000*10);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("task  end:"+getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, 1000*5,1000*5);
  }
}

与前面代码相比,我们只改了2处代码和修改了下打印,一是将run方法中的sleep改为了10秒,二是将任务的执行周期改为5秒。也就说任务的执行耗时超过了任务重复执行的间隔。运行程序,前面的输出如下:

main start:2016-01-14 09:03:51
task begin:2016-01-14 09:03:56
task   end:2016-01-14 09:04:06
task begin:2016-01-14 09:04:06
task   end:2016-01-14 09:04:16
task begin:2016-01-14 09:04:16
task   end:2016-01-14 09:04:26
task begin:2016-01-14 09:04:26
task   end:2016-01-14 09:04:36
task begin:2016-01-14 09:04:36
task   end:2016-01-14 09:04:46
task begin:2016-01-14 09:04:46
task   end:2016-01-14 09:04:56

可以看出,每个任务执行完成后,会立即执行下一个任务。因为从任务开始执行到任务完成的耗时已经超过了任务重复的间隔时间,所以会重复执行。

五、定时执行任务(重复固定时间点执行)

我们来实现这样一个功能,每天的凌晨1点定时执行一个任务,这在很多系统中都有这种功能,比如在这个任务中完成数据备份、数据统计等耗时、耗资源较多的任务。代码如下:

import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
 
public class TimerDemo {
  public static String getCurrentTime() {
    Date date = new Date();
    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
    return sdf.format(date);
  }
 
  public static void main(String[] args) throws InterruptedException {
    System.out.println("main start:" + getCurrentTime());
    startTimer();
  }
 
  public static void startTimer() {
    TimerTask task = new TimerTask() {
      @Override
      public void run() {
        System.out.println("task begin:" + getCurrentTime());
        try {
          Thread.sleep(1000 * 20);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        System.out.println("task  end:" + getCurrentTime());
      }
    };
    Timer timer = new Timer();
    timer.schedule(task, buildTime(), 1000 * 60 * 60 * 24);
  }
 
  private static Date buildTime() {
    Calendar calendar = Calendar.getInstance();
    calendar.set(Calendar.HOUR_OF_DAY, 1);
    calendar.set(Calendar.MINUTE, 0);
    calendar.set(Calendar.SECOND, 0);
    Date time = calendar.getTime();
    if (time.before(new Date())) {
      //若果当前时间已经是凌晨1点后,需要往后加1天,否则任务会立即执行。
      //很多系统往往系统启动时就需要立即执行一次任务,但下面又需要每天凌晨1点执行,怎么办呢?
      //很简单,就在系统初始化话时单独执行一次任务(不需要用定时器,只是执行那段任务的代码)
      time = addDay(time, 1);
    }
    return time;
  }
 
  private static Date addDay(Date date, int days) {
    Calendar startDT = Calendar.getInstance();
    startDT.setTime(date);
    startDT.add(Calendar.DAY_OF_MONTH, days);
    return startDT.getTime();
  }
 
}

因为是间隔24小时执行,没法等待观察输出。

六、小结

本文介绍了利用java Timer类如何执行定时任务的机制。可以看出,还是有许多需要注意的方法。 本文中介绍的例子,每个定时器只对应一个任务。

本文介绍的内容可以满足大部分应用场景了,但还有一些问题,比如对于一个定时器包括多个任务?定时器取消后能否再次添加任务?Timer类中还有哪些方法可用? 这些问题,我们再后面的博文中介绍。

更多Java定时任务:利用java Timer类实现定时执行任务的功能相关文章请关注PHP中文网!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.