当前位置:网站首页 > Java教程 > 正文

java中的timer教程



在项目开发过程中,经常会遇到需要使用定时执行或延时执行任务的场景。比如我们在活动结束后自动汇总生成效果数据、导出Excel表并将文件通过邮件推送到用户手上,再比如微信运动每天都会在十点后向你推送个位数的微信步数。

本文将从各个场景介绍的使用方式以及可能会出现的问题。

是JDK提供的非常实用的工具类,用于计划在特定时间后执行的任务,可以只执行一次或定期重复执行。在JDK内部很多组件都是使用的实现定时任务或延迟任务。

可以创建多个对象的实例,每个对象都有且只有一个后台线程来执行任务。

首先我们可以看下Timer类的构造方法的API文档

1.Timer(): 创建一个新的计时器。

2.Timer(boolean isDaemon): 创建一个新的定时器,其关联的工作线程可以指定为守护线程。

3.Timer(String name): 创建一个新的定时器,其关联的工作线程具有指定的名称。

4.Timer(String name, boolean isDaemon): 创建一个新的定时器,其相关线程具有指定的名称,可以指定为守护线程。

Note: 守护线程是低优先级线程,在后台执行次要任务,比如垃圾回收。当有非守护线程在运行时,Java应用不会退出。如果所有的非守护线程都退出了,那么所有的守护线程也会随之退出。

接下来我们看下Timer类的实例方法的API文档

1.cancel(): 终止此计时器,并丢弃所有当前执行的任务。

2.purge(): 从该计时器的任务队列中删除所有取消的任务。

3.schedule(TimerTask task, Date time): 在指定的时间执行指定的任务。

4.schedule(TimerTask task, Date firstTime, long period): 从指定 的时间开始 ,对指定的任务按照固定的延迟时间重复执行 。

5.schedule(TimerTask task, long delay): 在指定的延迟之后执行指定的任务。

6.schedule(TimerTask task, long delay, long period): 在指定的延迟之后开始 ,对指定的任务按照固定的延迟时间重复执行 。

7.scheduleAtFixedRate(TimerTask task, Date firstTime, long period): 从指定的时间开始 ,对指定的任务按照固定速率重复执行 。

8.scheduleAtFixedRate(TimerTask task, long delay, long period): 在指定的延迟之后开始 ,对指定的任务按照固定速率重复执行。

和都是重复执行任务,区别在于是在任务成功执行后,再按照固定周期再重新执行任务,比如第一次任务从0s开始执行,执行5s,周期是10s,那么下一次执行时间是15s而不是10s。而是从任务开始执行时,按照固定的时间再重新执行任务,比如第一次任务从0s开始执行,执行5s,周期是10s,那么下一次执行时间是10s而不是15s。

1. 执行时间晚于当前时间

接下来我们将分别使用和用来在10秒后执行任务,并展示是否将的工作线程设置成守护线程对执行的影响。

首先我们创建类, 接下来我们的所有操作都会在这个类中执行, 在类中使用,代码如下

在程序的最开始,我们注册程序结束时执行的函数,它用来打印程序的结束时间,我们稍后将会用它来展示工作线程设置为守护线程与非守护线程的差异。接下来是程序的主体部分,我们记录了程序的执行时间,定时任务执行时所在的线程、定时任务的期望执行时间与实际执行时间。

程序运行后的实际执行效果

程序在定时任务执行结束后并没有退出,我们注册的生命周期函数也没有执行,我们将在稍后解释这个现象。接下来我们在类中使用, 来达到相同的在10秒钟之后执行的效果

程序运行后的实际执行效果

回到我们刚刚的问题上,为什么我们的程序在执行完定时任务后没有正常退出?我们可以从Java API中对Thread类的描述中找到相关的内容:

从这段描述中,我们可以看到,只有在两种情况下,Java虚拟机才会退出执行

1.手动调用方法,并且安全管理器允许进行退出操作

2.所有的非守护线程都结束了,要么是执行完方法,要么是在方法中抛出向上传播的异常

所有的在创建后都会创建关联的工作线程,这个关联的工作线程默认是非守护线程的,所以很明显我们满足第二个条件,所以程序会继续执行而不会退出。那么如果我们将的工作线程设置成守护线程会发生什么呢?

程序运行后的实际执行结果

可以看到我们的延迟任务还没有开始执行,程序就已经结束了,因为在我们的主线程退出后,所有的非守护线程都结束了,所以Java虚拟机会正常退出,而不会等待中所有的任务执行完成后再退出。

2. 执行时间早于当前时间

如果我们是通过计算来指定执行时间的话,那么不可避免会出现一个问题——计算后的时间是早于当前时间的,这很常见,尤其是Java虚拟机会在不恰当的时候执行垃圾回收,并导致STW(Stop the world)。接下来,我们将调整之前调用的代码,让它在过去的时间执行

程序运行后的执行结果

可以看到,当我们指定运行时间为过去时间时,的工作线程会立执行该任务。但是如果我们不是通过计算时间,而是期望延迟负数时间再执行,会发生什么呢?我们将调整之前调用的代码, 让他以负数延迟时间执行

程序运行后的执行结果

如果我们传入负数的延迟时间,那么会抛出异常,告诉我们不能传入负数的延迟时间,这似乎是合理的——我们传入过去的时间是因为这是我们计算出来的,而不是我们主观传入的。在我们使用需要注意这一点。

3. 向Timer中添加多个任务

接下来我们将分别向中添加两个延迟任务,为了更容易地控制两个任务的调度顺序和时间,我们让第一个任务延迟5秒,第二个任务延迟10秒,同时让第一个任务阻塞10秒后再结束,通过这种方式来模拟出长任务。

程序运行后的执行结果

可以看到,两个任务在同个线程顺序执行,而第一个任务因为阻塞了10秒钟,所以是在程序开始运行后的第15秒结束,而第二个任务期望在第10秒结束,但是因为第一个任务还没有结束,所以第二个任务在第15秒开始执行,与与其执行时间偏差5秒钟。在使用Timer时尽可能不要执行长任务或使用阻塞方法,否则会影响后续任务执行时间的准确性。

4. 周期性执行任务

接下来我们将会分别使用和实现周期性执行任务。为了节省篇幅,我们将只演示如何使用和来实现周期性执行任务,并介绍它们的差异。而其他的两个方法和具有相同的效果和差异,就不再赘述。

首先我们修改类,调用来实现第一次执行完延迟任务后,周期性地执行任务

修改后的代码和使用时的代码基本相同,我们额外添加计数器来记录任务的执行次数,方法调用添加了第三个参数,表示任务每次执行时到下一次开始执行的时间间隔,我们这里设置成1秒钟。

程序运行后的执行结果

可以看到,每次任务执行都会有一定时间的偏差,而这个偏差随着执行次数的增加而不断积累。这个时间偏差取决于Timer中需要执行的任务的个数,随着Timer中需要执行的任务的个数增加呈非递减趋势。因为这个程序现在只有一个任务在重复执行,因此每次执行的偏差不是很大,如果同时维护成百上千个任务,那么这个时间偏差会变得很明显。

接下来我们修改类,调用来实现周期性执行任务

方法和的效果基本相同,它们都可以达到周期性执行任务的效果,但是方法会修正任务的下一次期望执行时间,按照每一次的期望执行时间加上参数来计算出下一次期望执行时间,因此scheduleAtFixedRate是以固定速率重复执行的,而schedule则只保证两次执行的时间间隔相同。程序运行后的执行结果

5. 停止任务

尽管我们很少会主动停止任务,但是这里还是要介绍下任务停止的方式。停止任务的方式分为两种:停止单个任务和停止整个。

首先我们介绍如何停止单个任务,为了停止单个任务,我们需要调用的方法,并调用的方法来移除所有已经被停止了的任务(回顾我们之前提到的,过多停止的任务不清空会影响我们的执行时间)

首先我们创建了4096个任务,并让来调度它们,接下来我们把除了第0个任务外的其他4095个任务停止掉,并从中移除所有已经停止的任务。程序运行后的执行结果

我们可以看到,只有第0个任务再继续执行,而其他4095个任务都没有执行。接下来我们介绍如何使用的来停止整个的所有任务,其实很简单,只需要执行就可以。

在将所有的任务添加到后,我们执行对象的方法,为了更方便地表现出的工作线程也终止了,我们注册了生命周期方法,来帮我们在程序结束后打印结束时间。程序运行后的执行结果。

可以看到,在执行对象的ancel()方法后,的工作线程也随之结束,程序正常退出。

本文从介绍了java.util.Timer的使用方式,覆盖了我们日常使用中涉及到的绝大部分场景和可能会遇到的问题。在接下来的文章中还会从源码角度对java.util.Timer进行解析,敬请期待~

版权声明


相关文章:

  • Java编写前端教程2025-01-05 11:34:05
  • java博客教程2025-01-05 11:34:05
  • 小包小包java教程2025-01-05 11:34:05
  • java函数参数教程2025-01-05 11:34:05
  • java自动排班教程2025-01-05 11:34:05
  • 大学用的java教程2025-01-05 11:34:05
  • java语言程序设计视频教程2025-01-05 11:34:05
  • java教程5章2025-01-05 11:34:05
  • java开发教程软件2025-01-05 11:34:05
  • java教程资料2025-01-05 11:34:05