Maison  >  Article  >  Java  >  Tâches planifiées Java : utilisez la classe Java Timer pour implémenter la fonction de tâches de synchronisation

Tâches planifiées Java : utilisez la classe Java Timer pour implémenter la fonction de tâches de synchronisation

高洛峰
高洛峰original
2017-01-11 16:07:533627parcourir

1. Présentation

Pour implémenter la fonction de synchronisation de l'exécution des tâches en Java, deux classes sont principalement utilisées, les classes Timer et TimerTask. Parmi eux, Timer est utilisé pour exécuter des tâches spécifiées sur un thread d'arrière-plan selon un plan spécifié.

TimerTask est une classe abstraite, et ses sous-classes représentent une tâche qui peut être planifiée par Timer. Le code spécifique à exécuter est écrit dans la méthode run qui doit être implémentée dans TimerTask.

2. Regardons d'abord l'exemple le plus simple

Nous l'illustrons par du 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);
  }
}

Afin de faciliter l'observation des informations par impression, nous en avons ajouté la méthode principale Imprimez les informations et appelez Thread.sleep pour laisser le thread principal dormir pendant un moment. De plus, une méthode getCurrentTime pour obtenir la date actuelle est ajoutée à la classe.

Le code ci-dessus, dans la méthode startTimer, crée d'abord un objet TimerTask (la tâche à exécuter par le timer), puis crée un objet Timer, puis appelle la méthode planning de la classe Timer. La classe Timer possède plusieurs méthodes de planification avec différents paramètres. Ce qui est utilisé ici est :

public void schedule(TimerTask task, long delay)

La signification de cette méthode est que le minuteur retardera le délai (millisecondes) avant d'exécuter la tâche. Si le délai est négatif ou 0, la tâche sera exécutée immédiatement. Et il s'agit d'une tâche d'exécution unique, et la tâche ne sera pas exécutée de manière répétée (ou planifiée) dans le futur.

Pour la classe Timer, une méthode avec la même fonction est également fournie, comme suit :

public void schedule(TimerTask task, Date time)

La différence entre cette méthode et la méthode ci-dessus est que la méthode ci-dessus spécifie un délai en exécution pendant une période de temps. Cette méthode est Spécifier l'exécution à un moment précis. Notez que si l'heure actuelle du système a dépassé l'heure spécifiée par le paramètre time, la tâche sera exécutée immédiatement.

Lors de l'exécution du code ci-dessus, nous avons constaté que le programme imprimait immédiatement 2 messages similaires au suivant :

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

Parce que la valeur du paramètre de retard que nous transmettons ici à la méthode de planification est 0, la tâche sera exécutée immédiatement, donc l'heure imprimée par les deux instructions c'est pareil, c'est ce que ça devrait être. Vous pouvez modifier vous-même la valeur du délai entrant pour voir les changements dans les informations de sortie. Après environ 5 secondes (c'est-à-dire le temps de veille), un autre message a été imprimé :

fin principale : 2016-01-13 22:23:23

L'heure d'impression du message est la même chose que La différence dans la déclaration ci-dessus est de 5 secondes, ce qui est cohérent avec le paramètre de veille et est très raisonnable.

Mais nous constaterons un phénomène très intéressant. Nous constaterons que le processus ne se terminera pas. À ce moment-là, le fil principal est terminé. Cela montre qu'une fois la tâche terminée, le minuteur ne se termine pas. tâches en attente d'être exécutées plus tard, le thread d'arrière-plan créé dans le minuteur ne se fermera pas immédiatement. J'ai vérifié le document Java doc pertinent et expliqué que le thread du minuteur ne se fermera pas activement et devra attendre le garbage collection. Cependant, l'attente de Java pour le garbage collection ne peut pas être contrôlée par le code lui-même, mais est contrôlée par la machine virtuelle.

Après quelques recherches, j'ai découvert que le thread timer sera créé lorsque l'objet Timer sera créé et que l'instruction Timer timer = new Timer(); C'est-à-dire que même si le code ci-dessus ne contient pas l'instruction timer.schedule(task, 0);, le programme ne se terminera pas. Cela semble tout à fait déraisonnable. J'ai étudié à nouveau le code source de la classe Timer et j'ai découvert qu'elle possède également un constructeur avec des paramètres booléens :

public Timer(boolean isDaemon)

Comme vous pouvez le voir d'après le nom du paramètre, si la valeur du paramètre est vraie, alors le Timer créé Le thread du minuteur est un thread démon. La signification du thread démon est que lorsque tous les threads de travail du processus Java se terminent, le thread démon se ferme automatiquement.

Pour le moment, il nous suffit de modifier le code de création de l'objet Timer dans l'exemple ci-dessus en : Timer timer = new Timer(true);

Après avoir découvert que le programme est en cours d'exécution , attendez le thread principal (le thread principal n'est pas). Une fois le thread démon (qui est un thread de travail) terminé, le programme se terminera, ce qui signifie que le thread du timer se ferme également. Cela signifie qu'après avoir ajouté le paramètre true, le créé le thread est un thread démon.

Mais le problème est que dans les scénarios d'application réels, de nombreux threads de travail sont en cours d'exécution et le programme ne se terminera pas par hasard. Alors, que devons-nous faire si nous voulons que la minuterie se termine ou se ferme immédiatement ? Nous le présenterons ci-dessous.

3. Quitter le minuteur

La classe Timer fournit une méthode d'annulation pour annuler le minuteur. L’appel de la méthode Cancel mettra fin à ce minuteur et supprimera toutes les tâches actuellement planifiées. Cela n'interfère pas avec la tâche en cours d'exécution (si elle existe). Une fois le minuteur terminé, son thread d'exécution est également terminé et plus aucune tâche ne peut être planifiée pour lui.

Notez qu'en appelant cette méthode dans la méthode run de la tâche timer appelée par ce timer, vous pouvez absolument vous assurer que la tâche en cours d'exécution est la dernière tâche exécutée par ce timer. Cette méthode peut être appelée à plusieurs reprises, mais le deuxième appel et les suivants n’ont aucun effet.

Regardons un autre exemple de 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());
    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;
  }
}

Exécutez le programme, le résultat est exactement le même que l'exemple ci-dessus. La différence est que lorsque la méthode principale se termine. Le processus se terminera activement, ce qui signifie que le thread du minuteur a été fermé.

Parce que nous avons appelé la méthode Cancel dans la méthode principale. Notez que si vous n'appelez pas la méthode Cancel dans la méthode run de TimerTask, vous devez faire attention à vous assurer que la tâche que vous souhaitez exécuter a démarré ou est terminée, sinon si la tâche n'a pas commencé son exécution. Appelez simplement Cancel et toutes les tâches ne seront pas exécutées. Par exemple, le code ci-dessus,

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

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn