Java中定时任务调度的实现
在工作中遇到一个需求,需要定时自动执行某项功能,这就需要用到定时任务了。
定时任务调度:基于给定的时间点,给定的时间间隔或者给定的执行次数自动执行任务。
定时任务调度的几种实现方式:
Timer:
Timer由JDK自带,不需要引入多余的jar。Java自带的java.util.Timer
类,这个类允许你调度一个java.util.TimerTask
任务。Timer只有一个后台线程执行任务,使用这种方式可以让你的程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
Quartz:
使用Quartz,这是一个功能比较强大的的调度器,可以让你的程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂,Quartz拥有后台执行线程池能够使用多个线程。
Spring Task:
Spring3.0以后自带的task,可以将它看成一个轻量级的Quartz,而且使用起来比Quartz简单许多。
Timer代码实现
首先写个需要定时实现的逻辑,这里是简单输出格式化后的时间
public class MyTimerTask extends TimerTask{ @Override public void run() { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(df.format(System.currentTimeMillis())); } }
然后写定时任务调度类
public class MyTimer { public static void main(String[] args){ //创建timer实例 Timer timer=new Timer(); //创建一个MyTimerTask实例 MyTimerTask myTimerTask=new MyTimerTask(); //通过timer定时调用myTimerTask中的run方法,因为 TimerTask 类实现了 Runnable 接口。 timer.schedule(myTimerTask,10*1000,2*1000); } }
这里的重点是Timer中schedule()方法的三个参数
第一个参数:是 就是我们刚刚写的MyTimerTask
类,这里需要继承TimerTask
,并实现 run()
方法,因为 TimerTask
类实现了 Runnable
接口。
第二个参数:是程序启动后,timer定时器第一次调用run方法的时间,0表示不指时间,立刻调用。(10*1000表示10秒,因为参数单位是毫秒)
第三个参数:是指第一次调用之后,从第二次开始每隔多长的时间调用一次 run() 方法。单位同样是毫秒。
拓展:
其实Timer中schedule()方法一共有四种用法,刚刚只是其中一种
schedule(task, time):time为
Date
类型,在指定时间执行一次。schedule(task, firstTime, period):firstTime为
Date
类型,从firstTime
时刻开始,每隔period
毫秒执行一次。schedule(task, delay):从现在起过
delay
毫秒执行一次 scheduleAtFixedRate(task,delay,period)
和schedule(task,delay,period)
基本一样scheduleAtFixedRate(task,firstTime,period)
和schedule(task,firstTime,period)
基本一样 区别可以参考: https://blog.csdn.net/gtuu0123/article/details/6040159
测试结果:
每隔两秒输出一次时间,第二个参数在截图上体现不了,需要自己敲出来感受。
注:时效性要求较高的多任务并发作业,复杂的任务的调度不推荐使用
Spring Task
@Configuration @EnableScheduling public class SchedulingConfig { @Scheduled(cron = "0/5 * * * * ?") // 每5秒执行一次 public void Scheduled(){ System.out.println("每5秒在控制台打印一次"+new Date()); } }
结果如下
spring task 在计算时间的时候,是根据当前服务器的系统时间进行计算.
如果是每10秒执行一次的话,那么它是从系统时间的0,10,20秒进行计算的.
如果是每1分钟执行一次的话,那么它是从系统时间的1分钟,2分钟进行计算的
如何动态修改cron
参数
代码解释 一开始设定的是每20秒执行一次,只要访问下
http://localhost:8080/changeExpression
就可以改成每10秒执行一次了
@RestController @EnableScheduling public class ConfigTask implements SchedulingConfigurer{ private String expression="0/20 * * * * *"; @RequestMapping("/changeExpression") public String changeExpression(){ expression="0/10 * * * * *"; return "changeExpression"; } @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { Runnable task=new Runnable() { @Override public void run() { System.out.println("TaskCronChange task is running ... "+ new Date()); } }; Trigger trigger=new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { CronTrigger cronTrigger=new CronTrigger(expression); return cronTrigger.nextExecutionTime(triggerContext); } }; scheduledTaskRegistrar.addTriggerTask(task,trigger); } }
.nextExecutionTim()
方法是为了返回一个date类型的数据
看起来很多代码,其实就是先实现SchedulingConfigurer
接口。
实现里面的方法,缩小成就这样
public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { Runnable task=new Runnable() {...}; Trigger trigger=new Trigger() {...}; scheduledTaskRegistrar.addTriggerTask(task,trigger); }
项目需求:
如果有个这样的场景,在某一段时间内要执行特定的操作
@RestController @EnableScheduling public class ConfigTask implements SchedulingConfigurer{ private String expression="0/20 * * * * *"; @RequestMapping("/changeExpression") public String changeExpression(){ expression="0/10 * * * * *"; return "changeExpression"; } public void println(long start,long end){ Date date=new Date(); long time=date.getTime()/1000; if(start<time&&time<end){ System.out.println("TaskCronChange task is running ... "+ new Date()); }else { System.out.println("TaskCronChange task is stopping ... "+ new Date()); } } @Override public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) { Runnable task=new Runnable() { @Override public void run() { println(1494306514,1494306634); } }; Trigger trigger=new Trigger() { @Override public Date nextExecutionTime(TriggerContext triggerContext) { CronTrigger cronTrigger=new CronTrigger(expression); return cronTrigger.nextExecutionTime(triggerContext); } }; scheduledTaskRegistrar.addTriggerTask(task,trigger); } }
把时间转化成时间戳,再去对比时间
结果如下
动态添加修改删除定时任务
1.首先需要重新认识一个类ThreadPoolTaskScheduler
:线程池任务调度类,能够开启线程池进行任务调度。
2.ThreadPoolTaskScheduler.schedule()
方法会创建一个定时计划ScheduledFuture
,在这个方法需要添加两个参数,Runnable
(线程接口类) 和CronTrigger
(定时任务触发器)
3.在ScheduledFuture
中有一个cancel可以停止定时任务。
@RestController @EnableScheduling public class DynamicTask { @Autowired private ThreadPoolTaskScheduler threadPoolTaskScheduler; private ScheduledFuture<?> future; @Bean public ThreadPoolTaskScheduler threadPoolTaskScheduler() { return new ThreadPoolTaskScheduler(); } @RequestMapping("/startCron") public String startCron() { future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("0/5 * * * * *")); System.out.println("DynamicTask.startCron()"); return "startCron"; } @RequestMapping("/stopCron") public String stopCron() { if (future != null) { future.cancel(true); } System.out.println("DynamicTask.stopCron()"); return "stopCron"; } @RequestMapping("/changeCron10") public String startCron10() { stopCron();// 先停止,在开启. future = threadPoolTaskScheduler.schedule(new MyRunnable(), new CronTrigger("*//**//*10 * * * * *")); System.out.println("DynamicTask.startCron10()"); return "changeCron10"; } private class MyRunnable implements Runnable { @Override public void run() { System.out.println("DynamicTask.MyRunnable.run()," + new Date()); } } }
在私有类MyRunnable
中的run
方法中可以写入具体的定时任务逻辑
(a)我们首先了一个类DynamicTask
;
(b)定义了两个变量,threadPoolTaskScheduler
和future
其中future
是treadPoolTaskScheduler
执行方法schedule
的返回值,主要用于定时任务的停止。
(c)编写启动定时器的方法startCron()
;
(d)编写停止方法stopCron()
,这里编码的时候,需要注意下需要判断下future
为null
的时候,不然就很容易抛出NullPointerException
;
(e)编写修改定时任务执行周期方法changeCron10()
,这里的原理就是关闭之前的定时器,创新在创建一个新的定时器。
Quartz
Quartz是OpenSymphony开源组织在Job scheduling领域又一个开源项目,是一个完全由Java编写的开源作业调度框架。
官网地址:http://www.quartz-scheduler.org/
为在Java应用程序中进行作业调度提供了简单却强大的机制。Quartz允许开发人员根据时间间隔来调度作业。它实现了作业和触发器的多对多的关系,还能把多个作业与不同的触发器关联。
Quartz的特点
① 强大的调度功能,spring默认的调度框架, Quartz 很容易与 Spring 集成实现灵活可配置的调度功能,及时系统因故障关闭,任务调度现场的数据并不会丢失
② 灵活的应用方式,运行定义触发器的调度时间表,并可以对触发器和任务进行关联映射,Quartz提供了组件式的监听器,支持任务和调度的多种组合方式,支持调度数据的多种存储方式
③ 分布式和集群能力,Terracotta 收购后在原来功能基础上作了进一步提升。
Quartz核心概念
scheduler:任务调度器
trigger:触发器,用于定义任务调度时间规则
job:任务,即被调度的任务
Quartz任务调度基本实现原理
Quartz 任务调度的核心元素是 scheduler
, trigger
和job
,其中 trigger
和 job
是任务调度的元数据, scheduler
是实际执行调度的控制器。
trigger 是用于定义调度时间的元素,即按照什么时间规则去执行任务。
Quartz 中主要提供了四种类型的 trigger:SimpleTrigger
,CronTirgger
,DateIntervalTrigger
,和 NthIncludedDayTrigger
。这四种 trigger 可以满足企业应用中的绝大部分需求。
job用于表示被调度的任务。
主要有两种类型的 job:无状态的(stateless)和有状态的(stateful)
对于同一个trigger
来说,有状态的 job
不能被并行执行,只有上一次触发的任务被执行完之后,才能触发下一次执行。
Job 主要有两种属性:volatility
和 durability
,其中 volatility
表示任务是否被持久化到数据库存储,而 durability
表示在没有trigger
关联的时候任务是否被保留。两者都是在值为true
的时候任务被持久化或保留。
一个job
可以被多个trigger
关联,但是一个trigger
只能关联一个job
。
scheduler
由 scheduler
工厂创建:DirectSchedulerFactory
或者 StdSchedulerFactory
。StdSchedulerFactory
使用较多,因为 DirectSchedulerFactory
使用起来不够方便,需要作许多详细的手工编码设置。Scheduler
主要有三种:RemoteMBeanScheduler
, RemoteScheduler
和 StdScheduler
。
在maven项目中使用Quartz
在pom.xml
文件中添加quartz
的依赖:
版本(Apr 19, 2017)
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
首先我们需要定义一个任务类,该类需要继承Job
类,
添加execute(JobExecutionContext context)
方法,在这个方法中就是我们具体的任务执行的地方。
public class HelloJob implements Job{ @Override public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException { SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); System.out.println(df.format(System.currentTimeMillis())); } }
任务调度
public class APP { public static void main(String[] args) throws SchedulerException, InterruptedException { // 获取Scheduler实例 Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler(); scheduler.start(); //具体任务. JobDetail jobDetail = JobBuilder.newJob(HelloJob.class) .withIdentity("job1","group1").build(); //触发时间点. (每5秒执行1次.) SimpleScheduleBuilder simpleScheduleBuilder = SimpleScheduleBuilder .simpleSchedule().withIntervalInSeconds(5).repeatForever(); Trigger trigger = TriggerBuilder.newTrigger() .withIdentity("trigger1","group1") .startNow() .withSchedule(simpleScheduleBuilder).build(); // 交由Scheduler安排触发 scheduler.scheduleJob(jobDetail,trigger); } }
测试结果:
结果就是每隔5秒输出一次时间此外还会输出关于触发器的一些信息
低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
C++程序设计基础(8)main函数
注:读《程序员面试笔记》笔记总结 1.知识点 (2)main函数的形式 1 //first type 2 int main() 3 //second type 4 int main(int argc,char *argv[]) 不推荐使用void格式,以上两种方式函数以return 0结束; argc(argument count):代表参数的个数; argv(argument value):代表命令行输入的参数,其中argv[0]是程序名; 2.面试题 2.1键鼠main函数执行前后发生了什么 答案:main函数第一行代码执行之前会调用全局对象和静态对象的构造函数,初始化全局变量和静态变量;main函数 最后一行代码执行之后会调用atexit中注册的函数,并且调用顺序与注册顺序相反。
- 下一篇
Java数据库的那些事(全是干货)
谈到数据库,大家第一想法就是怎么去优化,怎么让查询操作更快。我认为最好的方式就是从开始数据库设计的时候就要尽量考虑周全。如果不幸是个老项目,就得从优化入手了。接下就从设计和优化谈一下我的一些认识和经验。 1:数据库的设计 一个好的数据库设计方案对于数据库的性能常常会起到事半功倍的效果。数据库的设计包含数据库架构和业务表的设计。 我刚整理了一套2018最新的0基础入门和进阶教程,无私分享,加Java学习裙 :六七八,二四一,五六三 即可获取,内附:开发工具和安装包,以及系统学习路线图 1)数据库架构 根据不同的数据量和访问量,来设计不同的架构。适合自己的才是最好的。 单实例:数据读取和写入都是一个数据库实例。(备份实例不算在内)。这个适用于小型的企业内部系统。缺点是只适合数据量少的场景,优点是能达到数据的强一致性。 垂直拆分,多实例。不同的业务走不同的实例。同样也是适用于单个业务,数据量不大,并且每个业务相对独立,不产生关联。 读写分离,主从架构。通过主从结构,主库抗写压力,通过从库来分担读压力。适用于写少读多,数据一致性的实时性要求不高的应用。 主从,集群结构。适用于写多,读也多...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2配置默认Tomcat设置,开启更多高级功能
- Hadoop3单机部署,实现最简伪集群
- CentOS7,CentOS8安装Elasticsearch6.8.6
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- CentOS7安装Docker,走上虚拟化容器引擎之路
- CentOS8编译安装MySQL8.0.19
- Docker安装Oracle12C,快速搭建Oracle学习环境