Heim  >  Artikel  >  Java  >  Geplante Java-Aufgaben: Verwenden Sie die Java-Timer-Klasse, um die Funktion der Zeitsteuerung von Aufgaben zu implementieren

Geplante Java-Aufgaben: Verwenden Sie die Java-Timer-Klasse, um die Funktion der Zeitsteuerung von Aufgaben zu implementieren

高洛峰
高洛峰Original
2017-01-11 16:07:533562Durchsuche

1. Übersicht

Um die Funktion der zeitlichen Ausführung von Aufgaben in Java zu implementieren, werden hauptsächlich zwei Klassen verwendet, die Klassen Timer und TimerTask. Unter anderem wird Timer verwendet, um bestimmte Aufgaben in einem Hintergrundthread gemäß einem bestimmten Plan auszuführen.

TimerTask ist eine abstrakte Klasse und ihre Unterklassen stellen eine Aufgabe dar, die von Timer geplant werden kann. Der spezifische auszuführende Code wird in die Ausführungsmethode geschrieben, die in TimerTask implementiert werden muss.

2. Schauen wir uns zuerst das einfachste Beispiel an

Wir veranschaulichen es durch Code

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);
  }
}

Um die Beobachtung von Informationen durch Drucken zu erleichtern, haben wir einige hinzugefügt Die Hauptmethode gibt Informationen aus und ruft Thread.sleep auf, um den Hauptthread eine Weile schlafen zu lassen. Darüber hinaus wird der Klasse eine getCurrentTime-Methode zum Abrufen des aktuellen Datums hinzugefügt.

Der obige Code erstellt in der startTimer-Methode zunächst ein TimerTask-Objekt (die vom Timer auszuführende Aufgabe), erstellt dann ein Timer-Objekt und ruft dann die Schedule-Methode der Timer-Klasse auf. Die Timer-Klasse verfügt über mehrere Zeitplanmethoden mit unterschiedlichen Parametern. Was hier verwendet wird, ist:

public void schedule(TimerTask task, long delay)

Die Bedeutung dieser Methode besteht darin, dass der Timer die Verzögerung (Millisekunden) verzögert, bevor er die Aufgabe ausführt. Wenn die Verzögerung negativ oder 0 ist, wird die Aufgabe sofort ausgeführt. Und es handelt sich um eine einmalige Ausführungsaufgabe, und die Aufgabe wird in Zukunft nicht wiederholt (oder geplant) ausgeführt.

Für die Timer-Klasse wird auch eine Methode mit derselben Funktion wie folgt bereitgestellt:

public void schedule(TimerTask task, Date time)

Der Unterschied zwischen dieser Methode und der obigen Methode besteht darin, dass die obige Methode eine Verzögerung angibt Diese Methode wird für einen bestimmten Zeitraum ausgeführt. Beachten Sie, dass die Aufgabe sofort ausgeführt wird, wenn die aktuelle Zeit des Systems die durch den Parameter time angegebene Zeit überschreitet.

Beim Ausführen des obigen Codes haben wir festgestellt, dass das Programm sofort zwei Meldungen ähnlich der folgenden ausgab:

Hauptstart:2016-01-13 22:23:18
task run:2016-01-13 22:23:18

Da der Verzögerungsparameterwert, den wir hier an die Schedule-Methode übergeben, 0 ist, wird die Aufgabe sofort ausgeführt, also die von den beiden Anweisungen gedruckte Zeit ist das Gleiche, so sollte es sein. Sie können den Eingangsverzögerungswert selbst ändern, um Änderungen in den Ausgabeinformationen zu sehen. Nach etwa 5 Sekunden (also der Ruhezeit) wurde eine weitere Nachricht gedruckt:

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

Der Zeitpunkt des Druckens der Nachricht ist Das Gleiche wie Der Unterschied in der obigen Aussage beträgt 5 Sekunden, was mit der Schlafeinstellung übereinstimmt und sehr vernünftig ist.

Aber wir werden feststellen, dass der Prozess zu diesem Zeitpunkt nicht beendet wird. Dies zeigt, dass die Aufgabe nach Ablauf des Timers abgeschlossen ist Bei Aufgaben, die darauf warten, später ausgeführt zu werden, wird der im Timer erstellte Hintergrundthread nicht sofort beendet. Ich habe das entsprechende Java-Dokument überprüft und erklärt, dass der Timer-Thread nicht aktiv beendet wird und auf die Garbage Collection warten muss. Das Warten von Java auf die Garbage Collection kann jedoch nicht durch den Code selbst gesteuert werden, sondern wird von der virtuellen Maschine gesteuert.

Nach einigen Recherchen habe ich herausgefunden, dass der Timer-Thread erstellt wird, wenn das Timer-Objekt erstellt und die Anweisung Timer timer = new Timer(); ausgeführt wird. Das heißt, selbst wenn der obige Code die Anweisung timer.schedule(task, 0); nicht enthält, wird das Programm nicht beendet. Das fühlt sich ziemlich unvernünftig an. Ich habe den Quellcode der Timer-Klasse noch einmal studiert und festgestellt, dass sie auch einen Konstruktor mit booleschen Parametern hat:

public Timer(boolean isDaemon)

Wie Sie am Parameternamen erkennen können, gilt: Wenn der Parameterwert wahr ist, dann der Timer erstellt Der Timer-Thread ist ein Daemon-Thread. Die Bedeutung des Daemon-Threads besteht darin, dass der Daemon-Thread automatisch beendet wird, wenn alle Arbeitsthreads im Java-Prozess beendet werden.

Zu diesem Zeitpunkt müssen wir nur den Code zum Erstellen des Timer-Objekts im obigen Beispiel ändern in: Timer timer = new Timer(true);

Nachdem wir festgestellt haben, dass das Programm ausgeführt wird , Warten Sie auf den Hauptthread (der Hauptthread ist nicht) Nach dem Ende des Daemon-Threads (der ein Arbeitsthread ist) wird das Programm beendet, was bedeutet, dass auch der Timer-Thread beendet wird. Dies bedeutet, dass nach dem Hinzufügen des Parameters true der erstellte Thread beendet wird Thread ist ein Daemon-Thread.

Das Problem besteht jedoch darin, dass in realen Anwendungsszenarien viele Arbeitsthreads ausgeführt werden und das Programm nicht zufällig beendet wird. Was sollen wir also tun, wenn wir möchten, dass der Timer sofort beendet oder geschlossen wird? Wir werden dies im Folgenden vorstellen.

3. Beenden des Timers

Die Timer-Klasse bietet eine Abbruchmethode zum Abbrechen des Timers. Der Aufruf der Methode cancel beendet diesen Timer und verwirft alle derzeit geplanten Aufgaben. Dies beeinträchtigt nicht die aktuell ausgeführte Aufgabe (falls vorhanden). Sobald der Timer beendet ist, wird auch sein Ausführungsthread beendet und es können keine weiteren Aufgaben für ihn geplant werden.

Beachten Sie, dass Sie durch den Aufruf dieser Methode innerhalb der Ausführungsmethode der von diesem Timer aufgerufenen Timer-Aufgabe absolut sicherstellen können, dass die ausgeführte Aufgabe die letzte von diesem Timer ausgeführte Aufgabe ist. Diese Methode kann wiederholt aufgerufen werden; der zweite und weitere Aufrufe haben jedoch keine Auswirkung.

Sehen wir uns einen anderen Beispielcode an:

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;
  }
}

Führen Sie das Programm aus. Die Ausgabe ist genau die gleiche wie im obigen Beispiel. Der Unterschied besteht darin, dass die Hauptmethode endet. Der Prozess wird aktiv beendet, was bedeutet, dass der Timer-Thread geschlossen wurde.

Weil wir die Abbruchmethode in der Hauptmethode aufgerufen haben. Beachten Sie, dass Sie darauf achten müssen, dass die Aufgabe, die Sie ausführen möchten, gestartet oder abgeschlossen wurde, wenn Sie die Abbruchmethode nicht in der Ausführungsmethode von TimerTask aufrufen. Andernfalls wurde die Ausführung der Aufgabe nicht gestartet. Rufen Sie einfach „cancel“ auf und alle Aufgaben werden nicht ausgeführt. Zum Beispiel der obige Code,

比如上面的代码,如果我们不在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中文网!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn