ホームページ  >  記事  >  Java  >  Java スケジュールされたタスク: Java Timer クラスを使用して、タスクのタイミングを計る機能を実装します。

Java スケジュールされたタスク: Java Timer クラスを使用して、タスクのタイミングを計る機能を実装します。

高洛峰
高洛峰オリジナル
2017-01-11 16:07:533613ブラウズ

1. 概要

Java でタスク実行のタイミングを計る機能を実装するには、主に Timer クラスと TimerTask クラスの 2 つのクラスが使用されます。このうち、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 クラスのスケジュール メソッドを呼び出します。 Timer クラスには、異なるパラメーターを持つ複数のスケジュール メソッドがあります。ここで使用されるのは:

public void schedule(TimerTask task, long delay)

このメソッドの意味は、タイマーがタスクを実行する前に遅延 (ミリ秒) を遅らせることです。遅延が負または 0 の場合、タスクはすぐに実行されます。また、これは 1 回限りの実行タスクであり、今後繰り返し実行 (またはスケジュール) されることはありません。

Timerクラスには以下のような同様の機能を持つメソッドも提供されています。

public void schedule(TimerTask task, Date time)

このメソッドと上記のメソッドの違いは、上記のメソッドは実行の遅延を指定しているのに対し、このメソッドは即時実行を指定していることです。特定の時点。システムの現在時刻がパラメータ time で指定された時刻を超えた場合、タスクは直ちに実行されることに注意してください。

上記のコードを実行すると、プログラムが次のような 2 つのメッセージをすぐに出力したことがわかりました:

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

ここでスケジュール メソッドに渡した遅延パラメーターの値は 0 であるため、タスクはすぐに実行され、2 つのステートメントによって出力される時間は同じであり、これは本来あるべきものです。受信遅延値を自分で変更して、出力情報の変化を確認できます。約 5 秒後 (つまり、スリープ時間)、別のメッセージが出力されました:

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

メッセージが出力された時間は、上記のステートメントとは 5 秒異なりました一貫したスリープ設定も非常に合理的です。

しかし、非常に興味深い現象が見つかります。この時点では、待機中のタスクがない場合でも、メインスレッドが終了しないことがわかります。後で実行されると、タイマーで作成されたバックグラウンド スレッドはすぐには終了しません。関連する Java ドキュメント ドキュメントを確認し、タイマー スレッドはアクティブに終了せず、ガベージ コレクションを待機する必要があると説明しました。ただし、Java のガベージ コレクションの待機はコード自体では制御できず、仮想マシンによって制御されます。

少し調べた結果、Timer オブジェクトを作成して Timer timer = new Timer(); ステートメントを実行すると、タイマー スレッドが作成されることがわかりました。つまり、上記のコードに timer.schedule(task, 0); という文が含まれていない場合でも、プログラムは終了しません。これはかなり無理があるように感じます。 Timer クラスのソース コードを再度調べたところ、Boolean パラメーターを含むコンストラクターもあることを発見しました:

public Timer(boolean isDaemon)

パラメーター名からわかるように、パラメーター値が true の場合、Timer によって作成されたタイマー スレッドはデーモンです。糸。 。デーモン スレッドの意味は、Java プロセス内のすべての作業スレッドが終了すると、デーモン スレッドが自動的に終了することです。

現時点では、上記の例で Timer オブジェクトを作成するコードを次のように変更するだけです: Timer timer = new Timer(true);

プログラムの実行後、メイン スレッド (メインスレッドはデーモンスレッドではなく、作業スレッドです) を終了します。 その後、プログラムは終了します。これは、タイマースレッドも終了したことを意味します。これは、パラメーター true を追加した後、デーモンスレッドとして作成されることを意味します。

しかし、問題は、実際のアプリケーションのシナリオでは、多数のワーカー スレッドが実行されており、プログラムが簡単に終了しないことです。では、タイマーをすぐに終了または閉じたい場合はどうすればよいでしょうか?以下に紹介していきます。

3. タイマーの終了

Timer クラスには、タイマーをキャンセルするための cancel メソッドが用意されています。 cancel メソッドを呼び出すと、このタイマーが終了し、現在スケジュールされているすべてのタスクが破棄されます。これは、現在実行中のタスク (存在する場合) には干渉しません。タイマーが終了すると、その実行スレッドも終了し、それ以上のタスクをスケジュールできなくなります。

このタイマーによって呼び出されるタイマー タスクの 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());
    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 メソッドがいつ終了するかです。プロセスはアクティブに終了します。これは、タイマー スレッドが閉じられたことを意味します。

mainメソッドでcancelメソッドを呼び出したためです。 TimerTask の run メソッド内で cancel メソッドを呼び出さない場合は、実行するタスクが開始または完了していることを確認し、タスクが実行を開始していない場合は注意する必要があることに注意してください。 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 までご連絡ください。