前言

现代的应用程序早已不是以前的那些由简单的增删改查拼凑而成的程序了,高复杂性早已是标配,而任务的定时调度与执行也是对程序的基本要求了。

很多业务需求的实现都离不开定时任务,例如,每月一号,移动将清空你上月未用完流量,重置套餐流量,以及备忘录提醒、闹钟等功能。

Java 系统中主要有三种方式来实现定时任务:

Timer和TimerTask

ScheduledExecutorService

三方框架 Quartz

下面我们一个个来看。

1>Timer和TimerTask

先看一个小 demo,接着我们再来分析其中原理:

这种方式的定时任务主要用到两个类,Timer 和 TimerTask。其中,TimerTask 继承接口 Runnable,抽象的描述一种任务类型,我们只要重写实现它的 run 方法就可以实现自定义任务。

而 Timer 就是用于定时任务调度的核心类,demo 中我们调用其 schedule 并指定延时 1000 毫秒,所以上述代码会在一秒钟后完成打印操作,接着程序结束。

那么,使用上很简单,两个步骤即可,但是其中的实现逻辑是怎样的呢?

Timer 接口

首先,Timer 接口中,这两个字段是非常核心重要的:

TaskQueue 是一个队列,内部由动态数组实现的最小堆结构,换句话说,它是一个优先级队列。而优先级参考下一次执行时间,越快执行的越排在前面,这一点我们回头再研究。

接着,这个 TimerThread 类其实是 Timer 的一个内部类,它继承了 Thread 并重写了其 run 方法,该线程实例将在构建 Timer 实例的时候被启动。

run 方法内部会循环的从队列中取任务,如果没有就阻塞自己,而当我们成功的向队列中添加了定时任务,也会尝试唤醒该线程。

我们也来看一下 Timer 的构造方法:

public Timer(String name) {

thread.setName(name);

thread.start();

}

再简单不过的构造函数了,为内部线程设置线程名,并启动该线程。

最后,我们着重看一下 Timer 中用于配置一个定时任务进任务队列的方法。

//在时刻 time 处执行任务

schedule(TimerTask task, Date time)

//延时 delay 毫秒后执行任务

schedule(TimerTask task, long delay)

//固定延时重复执行,firstTime为首次执行时间,

//往后没间隔 period 毫秒执行一次

schedule(TimerTask task, Date firstTime, long period)

//固定延时重复执行

//首次执行时间为当前时间延时 delay 毫秒

schedule(TimerTask task, long delay, long period)

//固定频率重复执行,每过 period 毫秒执行一次

scheduleAtFixedRate(TimerTask task, Date firstTime, long period)

//固定频率重复执行

scheduleAtFixedRate(TimerTask task, long delay, long period)

相信有了注释,这几个方法的区别与作用应该不难理解,但是其中有两个概念需要作一点区分。

==固定延时== VS ==固定频率==

固定延时:以任务的上一次 实际 执行时间做参考,往后延时 period 毫秒。

固定频率:任务的往后每一次执行时间都在任务提交的那一刻得到了确定,不论你上次任务是否意外延时了,定时定点执行下一次任务。

这两者的区别还是很大的,希望你能够理解清楚,接着我们以其中一个方法为例,看看底层实现。

以这个方法为例,其他重载方法的底层调用都是同样的,我们不去赘述。

这个方法的作用,我们再说一遍。

以当前时间为准,延时 delay 毫秒后第一次执行该任务,并且采取固定延时的方式,每隔 period 毫秒再次执行该任务。

开头的两个异常判断我们不再赘述,看看 sched 方法:

方法需要传入三个参数,参数 task 代表的需要执行的任务体,TimerTask 我们回头会详细介绍,这里你知道它代表了一个任务体即可。

参数 time 描述了该任务下一次执行的时刻,计算机底层是以毫秒描述时刻的,所以这里转换为 long 类型来描述时刻。

参数 period 是固定延时的毫秒数。

整个方法的逻辑我们可以总结概括一下,具体的代码就不一行行分析了,因为也不难。

首先使用任务队列的内置对象锁,锁住个队列。

接着再去锁住我们的 task,并修改其内部的一些属性字段值,nextExecutionTime 指明下一次任务执行时间,period 设置固定延时的毫秒数,修改 state 状态为计划中。

然后将 task 添加到任务队列,其中 add 方法内部会进行最小堆重构,参考的就是 nextExecutionTime 字段的值,越小优先级越高。

判断如果自己就是队列第一个任务,那么将唤醒 Timer 中阻塞了的任务线程。

可能会有人疑问,Timer 如何判断一个任务是否是重复执行的,还是单次执行就结束的?

答案在 TimerThread 的 run 方法里,有兴趣你可以去研究下,方法体比较多比较长,这里不做分析。

当我们构造 Timer 实例的时候,就会启动该线程,该线程会在一个死循环中尝试从任务队列上获取任务,如果成功获取就执行该任务并在执行结束之后做一个判断。

如果 period 值为零,则说明这是一次普通任务,执行结束后将从队列首部移除该任务。

如果 period 为负值,则说明这是一次固定延时的任务,修改它下次执行时间 nextExecutionTime 为当前时间减去 period,重构任务队列。

如果 period 为正数,则说明这是一次固定频率的任务,修改它下次执行时间为 上次执行时间加上 period,并重构任务队列。

其实,我也已经把 TimerThread 的 run 方法里最核心的逻辑也已经介绍了,建议大家亲自去研究研究具体代码的实现,你会对这一块的逻辑更清晰。

最后,我们看一看这个 Timer 它有哪些劣势的地方:

Timer 的背后只有一个线程,不管你有多少个任务,都只有一个工作线程,效率上必然是要打折扣的。

限于单线程,如果第一个任务逻辑上死循环了,后续的任务一个都得不到执行。

依然是由于单线程,任一任务抛出异常后,整个 Timer 就会结束,后续任务全部都无法执行。

所以你看,单线程的 Timer 带来了太多局限性,于是我们看它的替代者。

PS:本来计划再介绍下 TimerTask 这个抽象任务类的,但是发现实在没啥好介绍的,就是增加了两个字段,一个用于记录下一次该任务的执行时间,一个用于延时毫秒数。你也只需要重写其 run 方法即可。

2> ScheduledExecutorService

这个接口相信你一定眼熟,我告诉你在哪见过。

你看,它是我们异步框架中的接口,正好我们今天来介绍他,这样整个异步框架中所有的接口我们都分析过了。

ScheduledExecutorService中定义的这四个接口方法和 Timer 中对应的方法几乎一样,只不过 Timer 的 scheduled 方法需要在外部传入一个 TimerTask 的抽象任务。

而我们的 ScheduledExecutorService 封装的更加细致了,随便你传 Runnable 或是 Callable,我会在内部给你做一层封装,封装一个类似 TimerTask 的抽象任务类(ScheduledFutureTask)。

然后传入线程池,启动线程去执行该任务,而我们的 ScheduledFutureTask 重写的 run 方法是这样的:

如果 periodic 为 true 则说明这是一个需要重复执行的任务,否则说明是一个一次性任务。

所以实际执行该任务的时候,需要分类,如果是普通的任务就直接调用 run 方法执行即可,否则在执行结束之后还需要重置下下一次执行时间。

整体来说,ScheduledExecutorService 区别于 Timer 的地方就在于前者依赖了线程池来执行任务,而任务本身会判断是什么类型的任务,需要重复执行的在任务执行结束后会被重新添加到任务队列。

而对于后者来说,它只依赖一个线程不停的去获取队列首部的任务并尝试执行它,无论是效率上、还是安全性上都比不上前者。

所以,建议使用 ScheduledExecutorService 取代 Timer,当然,通过学习 Timer 会更有助于对 ScheduledExecutorService 的研究。

3> 三方框架 Quartz

除了上述两种定时任务框架外,Java 生态圈还存在一种开源的三方框架,他就是 Quartz。

Quartz 是一个功能完善的任务调度框架,支持集群环境下的任务调度,需要将任务调度状态序列化到数据库。

Quartz 已经是随着分布式概念的流行,成为企业级定时任务调度框架中的不二选择。

Quartz 这个框架的使用及与原理在本篇就不做介绍了,我们会在后续介绍分布式概念的时候再来介绍它与 SpringCloud 平台下的整合使用情况。

总结

以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对脚本之家的支持。

java实现定时任务 schedule_Java定时任务的三种实现方式相关推荐

  1. java源代码实例倒计时_Java倒计时三种实现方式代码实例

    写完js倒计时,突然想用java实现倒计时,写了三种实现方式 一:设置时长的倒计时: 二:设置时间戳的倒计时: 三:使用java.util.Timer类实现的时间戳倒计时 代码如下: package ...

  2. java jndi tomcat_tomcat下jndi的三种配置方式

    Java命名和目录接口(the Java naming and directory interface,JNDI)是一组在Java应用中访问命名和目录服务的API.命名服务将名称和对象联系起来,使得读 ...

  3. spring的@Scheduled 定时任务没反应(三种配置方式)

    第一种,很麻烦 配置文件 <!--扫描所在包--><context:component-scan base-package="com.xxx.schedule"/ ...

  4. java倒计时_Java倒计时三种实现方式代码实例

    写完js倒计时,突然想用java实现倒计时,写了三种实现方式 一:设置时长的倒计时: 二:设置时间戳的倒计时: 三:使用java.util.Timer类实现的时间戳倒计时 代码如下: package ...

  5. java如何实现定时任务_Java定时任务的三种实现方式

    前言 现代的应用程序早已不是以前的那些由简单的增删改查拼凑而成的程序了,高复杂性早已是标配,而任务的定时调度与执行也是对程序的基本要求了. 很多业务需求的实现都离不开定时任务,例如,每月一号,移动将清 ...

  6. java servlet接口开发_servlet三种实现方式之一实现servlet接口

    servlet有三种实现方式: 1.实现servlet接口 2.继承GenericServlet 3.通过继承HttpServlet开发servlet 第一种示例代码如下(已去掉包名): import ...

  7. java中map的遍历方法_Java中Map的三种遍历方式

    集合中的三种遍历方式,如下代码: import java.util.Collection; import java.util.HashMap; import java.util.Iterator; i ...

  8. java定义数组_java中数组的三种定义方式_java中数组的定义及使用方法(推荐)...

    java中数组的三种定义方式 java中,数组是一种很常用的工具,今天我们来说说数组怎么定义 [java] view plain copy /** * 数组的三种定义方法 * 1.数组类型[] 数组名 ...

  9. Java 数组转成集合List三种方法和(数组、集合List、Set相互转换)

    Java 数组转成集合List 三种方法 package com.list;import java.util.ArrayList; import java.util.Arrays; import ja ...

  10. Java多线程的三种实现方式(重点看Collable接口实现方式)

    1.通过继承Thread类来实现多线程 在继承Thread类之后,一定要重写类的run方法,在run方法中的就是线程执行体,在run方法中,直接使用this可以获取当前线程,直接调用getName() ...

最新文章

  1. 一句话总结K均值算法
  2. 计算机行业可以开安装服务费,安装服务费税率是多少
  3. maven仲裁机制_maven仲裁机制
  4. 未来计算机控制器趋势,未来DCS控制系统技术发展4大趋势
  5. 重磅推荐!日立开源语义分割数据集标注工具Semantic Segmentation Editor
  6. 泛型使用思想,记一次java泛型使用经历
  7. Python源码深度解析—Python提供的C API
  8. HDU 5025 Saving Tang Monk【bfs搜索】【北大ACM/ICPC竞赛训练】
  9. 打开桌面计算机投屏到扩展屏,无线投屏新玩法——Windows电脑扩展屏幕投屏
  10. 磨刀不误砍柴工——利用JMP探索离群值
  11. 高等代数中涉及到“正交”的名词总结
  12. 2018技能高考计算机试题答案,计算机技能高考模拟试题一.doc
  13. 角频率、圆周频率、归一化频率的区别
  14. EuroBen Benchmark安装以及测试(By Robinvane Suen)
  15. React Hooks Ant table 显示/隐藏特定的列
  16. java打印任意边长的菱形
  17. 如何快速检索PDF文档中的关键词?
  18. linux http连接超时时间设置,Linux 下 HTTP连接超时
  19. 工作方案格式班主任工作职责
  20. microsoft outlook “未读邮件”收藏夹被误删

热门文章

  1. 【java】Java8 BiConsumer函数式接口
  2. 【Java】Java 反射机制浅析
  3. 【Kafka】kafka KafkaException: Exception while loading Zookeeper JAAS login context ‘Client‘
  4. 代码生成(Code Generation) 表达式编译
  5. [Maven] Project build error: 'packaging' with value 'jar' is invalid. Aggregator projects require
  6. 02-linux下 yum安装R环境和Rserve安装
  7. java自动转换需要的开头_字符串和数值型进行运算时,字符串如果不是数字开头,会自动转换成什么?...
  8. struts2的国际化(即实现网站整体中英文切换)实例
  9. c语言输入一串字符辨别奇偶,c语言设计输入一个正整数判断其中各个数字是否奇数偶数交替出现是输出yes不是输出no...
  10. 单源最短路径算法---Dijkstra