Java最高

通过。开始使用Spring 5和Spring Boot 2学习的春天课程:

>>查看课程

1.计时器-基础

计时器TimerTask是用于在后台线程中调度任务的java util类。一句话——TimerTask任务要执行和吗计时器是调度器

2.只安排一次任务

2.1。在一定的延迟之后

让我们从简单的开始运行单个任务在a的帮助下计时器:

@Test public void givenusingtimer_whenscheduletaskonce_thencorrect () {TimerTask task = new TimerTask() {public void run() {System.out。println("Task performed on: " + new Date() + "n" + "Thread's name: " + Thread. currentthread ().getName());}};Timer Timer = new Timer("Timer");长延迟= 1000L;计时器。计划(任务、延迟);}

现在,这将在一定延迟后执行任务的第二个参数表()方法。我们将在下一节中看到如何在给定的日期和时间安排任务。

注意,如果我们正在运行这是一个JUnit测试,我们应该添加一个线程。睡眠(延迟* 2)调用以允许计时器的线程在Junit测试停止执行之前运行任务。

2.2。在给定的日期和时间

现在,让我们看看计时器#计划(TimerTask,日期)方法,该方法需要日期而不是作为它的第二个参数,允许我们在特定的时刻安排任务,而不是在延迟之后。

这次,让我们假设我们有一个旧的遗留数据库,我们希望将其数据迁移到一个具有更好模式的新数据库中。

我们可以创建一个DatabaseMigrationTask处理迁移的类:

public class DatabaseMigrationTask extends TimerTask {private List oldDatabase;私人列表<字符串> newDatabase;public DatabaseMigrationTask(List oldDatabase, List newDatabase);oldDatabase = oldDatabase;这一点。newDatabase = newDatabase;} @Override public void run() {newDatabase.addAll(oldDatabase);}}

为了简单起见,我们用a表示这两个数据库列表字符串。简单地说,我们的迁移包括将第一个列表中的数据放入第二个列表。

要在期望的时刻执行这个迁移,函数的重载版本必须使用时间表()方法:

List oldDatabase = Arrays。asList(哈里森·福特,《凯莉·费雪》,《马克·哈米尔》);List newDatabase = new ArrayList<>();LocalDateTime twoSecondsLater = LocalDateTime.now().plusSeconds(2);Date twoSecondsLaterAsDate = Date.from(twoSecondsLater.atZone(ZoneId.systemDefault()).toInstant());新计时器()。时间表(new DatabaseMigrationTask(oldDatabase, newDatabase), twoSecondsLaterAsDate);

正如我们所看到的,我们将迁移任务和执行日期交给表()方法。

然后,在指定的时间执行迁移twoSecondsLater:

while (LocalDateTime.now().isBefore(twoSecondsLater)) {assert (newDatabase).isEmpty();thread . sleep (500);}为了(newDatabase) .containsExactlyElementsOf (oldDatabase);

当我们在这一刻之前,迁移不会发生。

3.安排一个可重复的任务

既然我们已经介绍了如何安排任务的单个执行,那么让我们看看如何处理可重复任务。

同样,有多种可能性计时器班级:我们可以设置重复来观察固定的延迟或固定的速率。

固定的延迟意味着执行将在最后一次执行开始后的一段时间内开始,即使它被延迟了(因此本身也被延迟了)

假设我们想每两秒安排一次任务,第一次执行需要1秒,第二次执行需要2秒,但是延迟了1秒。然后,第三次执行将从第五秒开始:

0 1 2 3年代5 s |——T1 --| |----- 2 s - | - 1 s - | - T2 -----| |----- 1 s, 2 s - | - | - 2 s - | - T3 - |

另一方面,一个固定的比率意味着每次执行都将遵守初始的时间表,无论之前的执行是否被延迟

让我们重复使用前面的示例,使用固定的速率,第二个任务将在三秒后启动(由于延迟)。但是,四秒后的第三个(考虑到每两秒执行一个的初始计划):

0 1 2 3 4年代| - T1 --| |----- 2 s - | - 1 s - | - T2 -----| |----- 2 s -----|----- 2 s - | - T3 - |

这两个原则已经介绍过了,让我们看看如何使用它们。

为了使用固定延迟调度,有两个额外的过载表()方法,每个方法都有一个额外的参数,以毫秒为单位表示周期性。

为什么两个过载?因为仍然有可能在特定时刻或特定延迟后启动任务。

至于固定费率,我们有两种scheduleAtFixedRate ()方法也以毫秒为周期。同样,我们有一个在给定日期和时间启动任务的方法,还有一个在给定延迟后启动任务的方法。

还值得一提的是,如果一个任务花费的时间超过了执行周期,那么它就会延迟整个执行链,不管我们是使用固定延迟还是固定速率。

3.1。有固定的延迟

现在,让我们假设我们想要实现一个时事通讯系统,每周向我们的追随者发送一封电子邮件。在这种情况下,重复的任务似乎是理想的。

所以,让我们每秒钟安排一次通讯,这基本上就是垃圾邮件,但由于发送是假的,我们可以开始了!

让我们先设计aNewsletterTask:

public class newsletter ttertask extends TimerTask {@Override public void run() {System.out;println("电子邮件发送到:" + LocalDateTime.ofInstant(Instant.ofEpochMilli(scheduledExecutionTime()), ZoneId.systemDefault())));}}

每次执行时,任务将打印它的计划时间,我们使用TimerTask # scheduledExecutionTime ()方法。

那么,如果我们想在固定延迟模式下每秒钟调度此任务,该怎么办呢?我们必须使用重载版本的表()我们之前说过金宝搏官网188be

新计时器()。时间表(new NewsletterTask(), 0, 1000); for (int i = 0; i < 3; i++) { Thread.sleep(1000); }

当然,我们只在少数情况下进行测试:

发送邮箱:2010-01-01 t10:50:30.860发送邮箱:2010-01-01 t10:50:31.860发送邮箱:2010-01-01 t10:50:32.861发送邮箱:2010-01-01 t10:50:33.861

正如我们所看到的,每次执行之间至少有一秒的间隔,但是它们有时会延迟一毫秒。这种现象是由于我们决定使用固定延迟重复。

3.2。固定利率

现在,如果我们使用固定速率的重复会怎样?那我们就得用scheduledAtFixedRate ()方法:

新计时器()。时间表AtFixedRate(new NewsletterTask(), 0, 1000); for (int i = 0; i < 3; i++) { Thread.sleep(1000); }

这一次,执行不会被前几次延迟:

Email sent at: 2019-01-01 t10:55:03.805 Email sent at: 2019-01-01 t10:55:04.805 Email sent at: 2019-01-01 t10:55:06.805

3.3。安排每天的任务

接下来,让我们每天运行一次任务:

@Test public void givenusingtimer_whenscheduledailytask_thecorrect () {TimerTask repeatedTask = new TimerTask() {public void run() {System.out. task_thecorrect ();println("Task performed on " + new Date());}};Timer Timer = new Timer("Timer");长延迟= 1000L;长周期= 1000L * 60L * 60L * 24L;计时器。scheduleAtFixedRate (repeatedTask、延迟期);}

4.取消计时器TimerTask

可以通过以下几种方式取消任务的执行:

4.1。取消TimerTask内部运行

通过调用TimerTask.cancel ()方法在run ()方法的实现TimerTask本身:

@Test public void givenusingtimer_whencancelingtimertask_thenccorrect () throws InterruptedException {TimerTask task = new TimerTask() {public void run() {System.out。println("Task performed on " + new Date());取消();}};Timer Timer = new Timer("Timer");计时器。scheduleAtFixedRate(任务、1000 l、1000 l);线程。睡眠(1000 l * 2);}

4.2。取消计时器

通过调用Timer.cancel ()方法计时器对象:

@Test public void givenusingtimer_whencancelingtimer_thenccorrect () throws InterruptedException {TimerTask task = new TimerTask() {public void run() {System.out。println("Task performed on " + new Date());}};Timer Timer = new Timer("Timer");计时器。scheduleAtFixedRate(任务、1000 l、1000 l);线程。睡眠(1000 l * 2);timer.cancel ();}

4.3。停止线程TimerTask内部运行

控件中的线程也可以停止运行方法,从而取消整个任务:

@Test public void givenUsingTimer_whenStoppingThread_thenTimerTaskIsCancelled() throws InterruptedException {TimerTask task = new TimerTask() {public void run() {System.out。println("Task performed on " + new Date());// TODO:在这里停止线程}};Timer Timer = new Timer("Timer");计时器。scheduleAtFixedRate(任务、1000 l、1000 l);线程。睡眠(1000 l * 2);}

注意TODO指令运行实现——为了运行这个简单的示例,我们需要实际停止线程。

在实际的自定义线程实现中,应该支持停止线程,但在这种情况下,我们可以忽略弃用,并使用simple停止API的线程类本身。

5.计时器vsExecutorService

您还可以很好地利用ExecutorService来调度计时器任务,而不是使用计时器。

下面是一个关于如何在指定的时间间隔内运行重复任务的快速示例:

@Test public void givenusingexecutorservice_whenschedulerepeatedtask_thenccorrect () throws InterruptedException {TimerTask repeatedTask = new TimerTask() {public void run() {System.out。println("Task performed on " + new Date());}};ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();长延迟= 1000L;长周期= 1000L;遗嘱执行人。时间表AtFixedRate(repeatedTask, delay, period, TimeUnit.MILLISECONDS); Thread.sleep(delay + period * 3); executor.shutdown(); }

那么它们之间的主要区别是什么呢计时器ExecutorService解决方案:

  • 计时器对系统时钟的变化敏感;ScheduledThreadPoolExecutor不是
  • 计时器只有一个执行线程;ScheduledThreadPoolExecutor可以配置任意数量的线程
  • 的内部抛出运行时异常TimerTask终止线程,这样接下来的计划任务就不会继续运行;与ScheduledThreadExecutor—当前任务将被取消,但其余任务将继续运行

6.结论

本教程演示了许多使用简单而灵活的方法计时器TimerTask内置在Java中的基础设施,用于快速调度任务。当然,如果您需要的话,Java世界中还有更复杂和完整的解决方案——例如石英图书馆-但这是一个很好的开始。

这些例子的实现可以在GitHub项目-这是一个基于eclipse的项目,所以它应该很容易导入和运行。

Java底部

通过。开始使用Spring 5和Spring Boot 2学习的春天课程:

>>查看课程
5评论
最古老的
最新的
内联反馈
查看所有评论
本文评论关闭!