文章目录
- 【README】
- 【1】Quartz任务调度框架
- 【1.1】Job调度任务
- 【1.2】任务调度触发器Trigger
- 【1.3】\*Quartz框架执行调度任务代码实践
- 【1.3.1】硬编码执行Quartz调度任务
- 【1.3.2】基于生产者模式执行quartz调度任务(推荐)
- 【2】spring集成Quartz
- 【2.1】spring管理quartz组件对象
- 【2.1.1】spring管理quartz的Job
- 【2.1.2】spring管理quartz的JobDetail
- 【2.1.3】spring管理quartz的Trigger
- 【2.1.4】spirng管理quartz的Scheduler
- 【2.2】spring启动quartz调度器执行调度任务
- 【3】JDK Timer定时器
- 【3.1】JDK Timer基础知识回顾
- 【3.2】定时器任务单次执行
- 【3.2.1】给定延迟时间之后执行单次任务
- 【3.2.2】在给定时间点执行单次任务
- 【3.3】 定时器任务重复执行
- 【3.3.1】在给定延迟时间后重复执行
- 【3.2.2】在给定时间点重复执行
- 【3.3.3】定时器任务重复执行注意点
- 【3.4】取消调度器与调度任务
- 【3.4.1】 在run方法里取消调度任务
- 【3.5】定时器Timer与调度线程池对比
- 【3.5.1】调度线程池执行调度任务回顾
- 【3.5.2】定时器与调度线程池ScheduledExecutorService对比
【README】
本文部分内容总结自《spring揭秘》,作者王福强,非常棒的一本书,墙裂推荐;
本文部分内容总结自: https://juejin.cn/post/7158071449314394119
本文代码参见: github-springDiscover-chapter31
【1】Quartz任务调度框架
1)Quartz:是一款开源的任务调度框架; 官网介绍参见: quartz-scheduler
- 官网介绍总结(翻译): quartz是一款功能丰富的开源作业调度库,可以集成几乎任何java应用,从最小的单体应用到最大的电子商务系统。
quartz能够用于创建简单或复杂的调度以执行数十,数百,数万个作业;这些作业的任务可以被定义为标准的java组件,可以执行你编程让它们做的任何操作。quartz调度器包含许多企业级功能,如支持JTA事务与集群。 - 应用场景: 定时执行业务逻辑(如数据同步,发送短信),每隔一段时间执行业务逻辑(如每隔5秒发送1次心跳);
2)相对于JDK的Timer之类的简单任务调度程序来说,Quartz拥有丰富的功能,如下:
- 允许批处理任务状态的持久化;
- 批处理任务的远程调度;
- 基于web的监控接口;
- 集群支持; (同一时刻只有一个任务执行;当一个任务宕机之后,其它服务会接管这个任务)
- 插件式的可扩展性;
3)Quartz组件清单 ;
- Job: 抽象调度任务(作业);
- JobDetail:封装job的详细信息,如job名称,组名称,job的class,JobDataMap等;
- Trigger:触发器,抽象job触发规则(如执行开始时间,结束时间,执行频率);
- Scheduler:调度器; 使用Trigger定义的规则执行job;
- JobBuilder:Job生成器;用于创建Job实例;
- TriggerBuilder:Trigger生成器;用于创建Trigger实例;
- JobExecutionContext:Job运行时上下文;
- JobDataMap: 封装参数键值对,用于Job与JobDetail的数据交互;
4)Quartz基本组件关系如下:
5)使用 quartz任务调度框架,需要引入对应maven依赖,如下:
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.2</version>
</dependency>
【1.1】Job调度任务
1)quartz中调度任务job定义
public interface Job {
void execute(JobExecutionContext var1) throws JobExecutionException;
}
2)调度任务实现job接口;
public class TomScheduleJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// do sth.
System.out.println("MyScheduleJob#当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【1.2】任务调度触发器Trigger
1)触发器:定义job触发规则;如每隔10分钟执行1次,每天早上7点,9点准时执行1次;
2)触发器Trigger定义如下:
public interface Trigger extends Serializable, Cloneable, Comparable<Trigger> {
long serialVersionUID = -3904243490805975570L;
int MISFIRE_INSTRUCTION_SMART_POLICY = 0;
int MISFIRE_INSTRUCTION_IGNORE_MISFIRE_POLICY = -1;
int DEFAULT_PRIORITY = 5;
TriggerKey getKey();
JobKey getJobKey();
String getDescription();
String getCalendarName();
JobDataMap getJobDataMap();
int getPriority();
boolean mayFireAgain();
Date getStartTime();
Date getEndTime();
Date getNextFireTime();
Date getPreviousFireTime();
Date getFireTimeAfter(Date var1);
Date getFinalFireTime();
int getMisfireInstruction();
TriggerBuilder<? extends Trigger> getTriggerBuilder();
ScheduleBuilder<? extends Trigger> getScheduleBuilder();
boolean equals(Object var1);
int compareTo(Trigger var1);
// ...
}
2)触发器的两种主要实现:
- SimpleTrigger:指定基于时间间隔的调度规则(触发规则);
- CronTrigger:指定基于cron表达式的调度规则;(包括但不限于时间间隔,还可以指定具体时间执行,触发规则更加灵活 )
【1.3】*Quartz框架执行调度任务代码实践
【1.3.1】硬编码执行Quartz调度任务
【TomQuartzJob】
public class TomQuartzJob implements Job {
@Override
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
// do sth.
System.out.println("TomQuartzJob#当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【TomQuartzJobMain】
public class TomQuartzJobMain {
public static void main(String[] args) throws Exception {
executeScheduleJob();
}
private static void executeScheduleJob() throws ParseException, SchedulerException {
// 新建触发器
SimpleTriggerImpl simpleTrigger =
new SimpleTriggerImpl(
"tomSimpleTrigger", "tomSimpleTriggerGroup", new Date()
, null, SimpleTriggerImpl.REPEAT_INDEFINITELY, 5000); // 每隔5秒执行1次
CronTriggerImpl cronTrigger = new CronTriggerImpl("tomSimpleTrigger", "tomSimpleTriggerGroup", "*/5 * * * * ?");
// 新建调度器
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
scheduler.start();
// 新建调度任务详情
JobDetailImpl tomJobDetail = new JobDetailImpl("tomJobDetail", TomQuartzJob.class);
// 新增调度任务(这里是新增,并没有执行,执行由触发器来负责)
// scheduler.scheduleJob(tomJobDetail, simpleTrigger);
scheduler.scheduleJob(tomJobDetail, cronTrigger);
}
}
【1.3.2】基于生产者模式执行quartz调度任务(推荐)
1)基于生成器模式执行调度任务: 参考 quick-start
【TomTimeRemindJob】
public class TomTimeRemindJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("=========================================");
context.getMergedJobDataMap().forEach((k,v)-> System.out.println(k + "=" + v));
context.getJobDetail().getJobDataMap().forEach((k,v)-> System.out.println(k + "=" + v));
System.out.println("TomTimeRemindJob now=" + BusiDatetimeUtils.getNowText());
}
}
【TomTimeRemindJobMain】
public class TomTimeRemindJobMain {
public static void main(String[] args) throws SchedulerException {
// 创建调度器实例
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
// 开启调度器
scheduler.start();
// 执行调度作业
executeScheduleJob(scheduler);
// 关闭调度器
// scheduler.shutdown();
}
private static void executeScheduleJob(Scheduler scheduler) throws SchedulerException {
// 创建JobDetail,并与TomTimeRemindJob 绑定
JobDetail jobDetail = JobBuilder.newJob(TomTimeRemindJob.class)
.withIdentity("tomJob01", "tomJob01Group")
.build();
// 立即触发作业执行,每5秒重复一次
Trigger simpleTrigger = TriggerBuilder.newTrigger()
.withIdentity("tomTrigger01", "tomTrigger01Group")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever())
.build();
// 告诉quartz使用我们的触发器调度作业
scheduler.scheduleJob(jobDetail, simpleTrigger);
}
}
【2】spring集成Quartz
1)通过上述代码【TomTimeRemindJobMain】, 本文发现使用Quartz框架执行调度任务需要依赖以下4个组件(Job, JobDetail, Trigger, Scheduler):
-
Job:调度任务;
-
JobDetail: 用于封装job详细信息;
-
Trigger:触发器,定义触发规则;
-
Scheduler:调度器,根据触发器的规则执行调度任务(作业);
2)可以想到,spring集成Quartz的底层原理 :把Job,JobDetail, SimpleTrigger, Scheduler对象创建与依赖关系装配由spring来完成,无需客户端实现; spring采用工厂方法FactoryBean来管理quartz调度任务所需组件;
3)spring集成Quartz需要新增maven依赖,如下:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>6.1.10</version>
</dependency>
【2.1】spring管理quartz组件对象
1)本章节部分内容总结自: spring-quartz-schedule
【2.1.1】spring管理quartz的Job
1)spring提供了QuartzJobBean用于抽象调度任务,自定义调度任务需要继承QuartzJobBean并重写executeInternal()方法;
2)QuartzJobBean定义:(注意:quartz的JobDetail封装了Job的class对象,由JobDetail实例化job;所以spring无需注册job)
public abstract class QuartzJobBean implements Job {
public QuartzJobBean() {
}
public final void execute(JobExecutionContext context) throws JobExecutionException {
try {
BeanWrapper bw = PropertyAccessorFactory.forBeanPropertyAccess(this);
MutablePropertyValues pvs = new MutablePropertyValues();
pvs.addPropertyValues(context.getScheduler().getContext());
pvs.addPropertyValues(context.getMergedJobDataMap());
bw.setPropertyValues(pvs, true);
} catch (SchedulerException var4) {
throw new JobExecutionException(var4);
}
this.executeInternal(context);
}
protected abstract void executeInternal(JobExecutionContext context) throws JobExecutionException;
}
【自定义job,继承QuartzJobBean】TomSpringQuartzJobBean
public class TomSpringQuartzJobBean extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
System.out.println("TomSpringQuartzJobBean #当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【2.1.2】spring管理quartz的JobDetail
1)在quartz中, job抽象了调度任务,而job的上下文信息由JobDetail封装,Job与JobDetail的数据交互通过JobDataMap实现;
2)spring注册JobDetail有两种方式:
- 方式1: JobDetailFactoryBean 注册JobDetail; (推荐) ;
- 方式2: 通过 MethodInvokingJobDetailFactoryBean 注册JobDetail ; (本文不介绍)
【springquartz.xml】 JobDetailFactoryBean 注册JobDetail
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通过 JobDetailFactoryBean 注册JobDetail -->
<bean class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="tomJob" />
<property name="group" value="tomJobGroup" />
<property name="jobClass" value="com.tom.springnote.chapter31schedule.springquartz.SpringScheduleJob" />
<property name="durability" value="true" />
<property name="description" value="tomJobDetail" />
<property name="jobDataMap">
<map>
<entry key="message" value="hello world" />
<entry key="city" value="chengdu" />
</map>
</property>
</bean>
</beans>
【2.1.3】spring管理quartz的Trigger
1)trigger触发器:定义了执行调度任务的规则, 如每5秒执行1次;
2)spring也是通过FactoryBean注册Trigger;
- SimpleTriggerFactoryBean (简单触发器)
- CronTriggerFactoryBean; (带有cron表达式的触发器)
【springquartz.xml】 SimpleTriggerFactoryBean 注册Trigger
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通过 JobDetailFactoryBean 注册JobDetail -->
<bean id="tomJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="tomJob" />
<property name="group" value="tomJobGroup" />
<property name="jobClass" value="com.tom.springnote.chapter31schedule.springquartz.SpringScheduleJob" />
<property name="durability" value="true" />
<property name="description" value="tomJobDetail" />
<property name="jobDataMap">
<map>
<entry key="message" value="hello world" />
<entry key="city" value="chengdu" />
</map>
</property>
</bean>
<!-- 通过 SimpleTriggerFactoryBean 注册触发器 -->
<bean class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="name" value="tomTrigger" />
<property name="group" value="tomTriggerGroup" />
<property name="jobDetail" ref="tomJobDetail" />
<property name="repeatInterval" value="5000" /> <!-- 单位毫秒,每5秒执行1次-->
<property name="repeatCount" value="-1" /> <!-- -1表示永远重复 -->
</bean>
<!-- 通过 CronTriggerFactoryBean 注册触发器 -->
<bean class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="tomCronTrigger" />
<property name="group" value="tomCronTriggerGroup" />
<property name="jobDetail" ref="tomJobDetail" />
<property name="cronExpression" ref="*/3 * * * * ?" /> <!-- 每3秒执行1次-->
</bean>
</beans>
3)补充: 多个trigger定义的规则可以同时作用于同一个JobDetail (或Job); 本文认为多个Trigger规则的组合太过复杂(1个trigger对应1个Job足够满足日常业务需求),本文不再深入研究多个trigger组合的情况;有兴趣的同学自行研究;
【2.1.4】spirng管理quartz的Scheduler
1)scheduler调度器:使用触发器Trigger定义的规则执行调度任务(job);
2)spring通过SchedulerFactoryBean注册scheduler;
【springquartz.xml】 SchedulerFactoryBean注册scheduler
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- 通过 JobDetailFactoryBean 注册JobDetail -->
<bean id="tomJobDetail" class="org.springframework.scheduling.quartz.JobDetailFactoryBean">
<property name="name" value="tomJob" />
<property name="group" value="tomJobGroup" />
<property name="jobClass" value="com.tom.springnote.chapter31schedule.springquartz.TomSpringQuartzJobBean" />
<property name="durability" value="true" />
<property name="description" value="tomJobDetail" />
<property name="jobDataMap">
<map>
<entry key="message" value="hello world" />
<entry key="city" value="chengdu" />
</map>
</property>
</bean>
<!-- 通过 SimpleTriggerFactoryBean 注册触发器 -->
<bean id="tomSimpleTrigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean">
<property name="name" value="tomTrigger" />
<property name="group" value="tomTriggerGroup" />
<property name="jobDetail" ref="tomJobDetail" />
<property name="repeatInterval" value="5000" /> <!-- 单位毫秒,每5秒执行1次-->
<property name="repeatCount" value="-1" /> <!-- -1表示永远重复 -->
</bean>
<!-- 通过 CronTriggerFactoryBean 注册触发器 -->
<bean id="tomCronTrigger" class="org.springframework.scheduling.quartz.CronTriggerFactoryBean">
<property name="name" value="tomCronTrigger" />
<property name="group" value="tomCronTriggerGroup" />
<property name="jobDetail" ref="tomJobDetail" />
<property name="cronExpression" ref="*/3 * * * * ?" /> <!-- 每3秒执行1次-->
</bean>
<!-- 通过SchedulerFactoryBean注册scheduler -->
<bean class="org.springframework.scheduling.quartz.SchedulerFactoryBean">
<property name="triggers"> <!-- 可以装配多个触发器,当然,日常开发1个足够 -->
<list>
<ref bean="tomSimpleTrigger" />
<ref bean="tomCronTrigger" />
</list>
</property>
</bean>
</beans>
【2.2】spring启动quartz调度器执行调度任务
1)spring容器ApplicationContext启动时,SchedulerFactoryBean所管理的调度器Scheduler跟着自动启动,且Scheduler随着ApplicationContext的关闭而自动关闭;
- 当调度任务的所有对象实例注册到ioc容器后,ApplicationContext启动时,Scheduler也启动,Scheduler立即开始执行调度任务;
【TomSpringQuartzJobBeanMain】
public class TomSpringQuartzJobBeanMain {
public static void main(String[] args) {
ClassPathXmlApplicationContext springContext = new ClassPathXmlApplicationContext("chapter31schedule/springquartz.xml");
((AbstractApplicationContext) springContext).registerShutdownHook();
}
}
【执行效果】 2个调度任务在执行(因为有2个触发器,tomSimpleTrigger-每5秒执行1次;tomCronTrigger-每3秒执行1次 );
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:23.623
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:24.002
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:27.000
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:28.505
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:30.000
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:33.010
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:33.501
TomSpringQuartzJobBean #当前时间=2024-10-13 21:42:36.003
【3】JDK Timer定时器
【3.1】JDK Timer基础知识回顾
1)Timer定时器组件:
- TimerTask:抽象调度任务;
- Timer:抽象调度器;用于定义任务触发规则并执行调度任务; 类似于肩负了quartz的Scheduler与Trigger职责;
- 只能配置简单规则,不能配置cron表达式;
2)Timer使用TimerTask抽象调度任务;自定义调度任务需要继承TimerTask;
- 由代码可知: TimerTask本质上是Runnable,即可以提交给线程的任务;
public abstract class TimerTask implements Runnable {
// ...
}
3)部分代码总结自: java-timer-and-timertask
【3.2】定时器任务单次执行
【3.2.1】给定延迟时间之后执行单次任务
1)在当前时间等待给定延迟时间之后,执行调度任务;
【OnceTimerTaskExecuteAfterDelayMain】给定延迟时间之后仅执行1次调度任务
public class OnceTimerTaskExecuteAfterDelayMain {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
}
};
// 通过jdk 定时器执行调度任务
Timer timer = new Timer();
System.out.println("当前时间=" + BusiDatetimeUtils.getNowText());
// 在给定延迟时间(5s)之后执行调度任务,仅执行1次
timer.schedule(timerTask, 5000);
System.out.println("after timer.schedule()新增调度任务之后");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 取消调度器执行(主线程结束)
timer.cancel();
System.out.println("主线程结束, 当前时间=" + BusiDatetimeUtils.getNowText());
}
}
public void schedule(TimerTask task, long delay) {
if (delay < 0)
throw new IllegalArgumentException("Negative delay.");
sched(task, System.currentTimeMillis()+delay, 0); // period=0 表示当前任务仅执行1次
}
private void sched(TimerTask task, long time, long period) {
// ...
}
【解说】period参数:表示2个相邻任务执行的时间间隔(单位毫秒);
- 若为0,表示仅执行1次 ;
- 若为整数,表示时间间隔;
【执行效果】
当前时间=2024-10-16 20:51:52.281
after timer.schedule()新增调度任务之后
线程id=15 当前时间=2024-10-16 20:51:57.298
主线程结束, 当前时间=2024-10-16 20:51:57.298
【3.2.2】在给定时间点执行单次任务
1)在给定时间点执行调度任务;
【OnceTimerTaskExecuteAtGivenTime】 在给定时间点执行调度任务,仅执行1次
public class OnceTimerTaskExecuteAtGivenTime {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
}
};
// 通过jdk 定时器执行调度任务
Timer timer = new Timer();
System.out.println("当前时间=" + BusiDatetimeUtils.getNowText());
// 在给定时间点(当前时间加5秒的时间)执行调度任务,仅执行1次
timer.schedule(timerTask, BusiDatetimeUtils.timeAfterSecond(5));
System.out.println("after timer.schedule()新增调度任务之后");
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 取消调度器执行(主线程结束)
timer.cancel();
System.out.println("主线程结束, 当前时间=" + BusiDatetimeUtils.getNowText());
}
}
public void schedule(TimerTask task, Date time) {
sched(task, time.getTime(), 0); // period=0 表示仅执行1次
}
【执行效果】
当前时间=2024-10-16 20:55:09.981
after timer.schedule()新增调度任务之后
线程id=15 当前时间=2024-10-16 20:55:14.984
主线程结束, 当前时间=2024-10-16 20:55:14.986
【3.3】 定时器任务重复执行
【3.3.1】在给定延迟时间后重复执行
1)使用Timer定时器在给定延迟时间后重复执行调度任务
【RepeatableTimerTaskExecuteAfterDelayMain】
public class RepeatableTimerTaskExecuteAfterDelayMain {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
}
};
// 通过jdk 定时器执行调度任务
Timer timer = new Timer();
System.out.println("当前时间=" + BusiDatetimeUtils.getNowText());
// 在给定延迟时间(5s)之后执行调度任务,间隔2秒重复执行
timer.schedule(timerTask, 5000, 2000);
System.out.println("after timer.schedule()新增调度任务之后");
try {
TimeUnit.SECONDS.sleep(10); // 睡眠10s后关闭定时器,以便调度任务可以重复执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 取消调度器执行(主线程结束)
timer.cancel();
System.out.println("主线程结束, 当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【执行效果】
当前时间=2024-10-16 21:01:49.933
after timer.schedule()新增调度任务之后
线程id=15 当前时间=2024-10-16 21:01:54.950
线程id=15 当前时间=2024-10-16 21:01:56.951
线程id=15 当前时间=2024-10-16 21:01:58.957
主线程结束, 当前时间=2024-10-16 21:01:59.946
【3.2.2】在给定时间点重复执行
【RepeatableTimerTaskExecuteAtGivenTime】
public class RepeatableTimerTaskExecuteAtGivenTime {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
}
};
// 通过jdk 定时器执行调度任务
Timer timer = new Timer();
System.out.println("当前时间=" + BusiDatetimeUtils.getNowText());
// 在给定时间点(当前时间加5秒的时间)执行调度任务,间隔2s重复执行
timer.schedule(timerTask, BusiDatetimeUtils.timeAfterSecond(5), 2000);
System.out.println("after timer.schedule()新增调度任务之后");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 取消调度器执行(主线程结束)
timer.cancel();
System.out.println("主线程结束, 当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【执行效果】
当前时间=2024-10-16 21:07:12.258
after timer.schedule()新增调度任务之后
线程id=15 当前时间=2024-10-16 21:07:17.260
线程id=15 当前时间=2024-10-16 21:07:19.261
线程id=15 当前时间=2024-10-16 21:07:21.275
主线程结束, 当前时间=2024-10-16 21:07:22.261
【3.3.3】定时器任务重复执行注意点
1) 若一个任务执行耗时大于调度间隔或周期period,则会延迟整个执行链;无论是在延迟时间后还是给定时间点执行 ;
【RepeatableTimerTaskDelayWholeChainAtGivenTime】
public class RepeatableTimerTaskDelayWholeChainAtGivenTime {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
try {
TimeUnit.SECONDS.sleep(5); // 模拟单个任务执行耗时5秒
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
};
// 通过jdk 定时器执行调度任务
Timer timer = new Timer();
System.out.println("当前时间=" + BusiDatetimeUtils.getNowText());
// 在给定时间点(当前时间加5秒的时间)执行调度任务,间隔2s重复执行
timer.schedule(timerTask, BusiDatetimeUtils.timeAfterSecond(1), 2000);
System.out.println("after timer.schedule()新增调度任务之后");
try {
TimeUnit.SECONDS.sleep(30); // 延迟30秒,等待执行重复调度任务
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 取消调度器执行(主线程结束)
timer.cancel();
System.out.println("主线程结束, 当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【执行效果】 程序中设置定时器每隔2s执行调度任务,而运行效果是每隔5s执行调度任务; 原因是任务执行睡眠5s模拟了业务逻辑耗时;当上一个任务执行完成后,下一个任务才会执行(即单个任务执行耗时过长会延迟整体任务调度);
当前时间=2024-10-16 21:36:40.218
after timer.schedule()新增调度任务之后
线程id=15 当前时间=2024-10-16 21:36:41.222
线程id=15 当前时间=2024-10-16 21:36:46.236
线程id=15 当前时间=2024-10-16 21:36:51.247
线程id=15 当前时间=2024-10-16 21:36:56.258
线程id=15 当前时间=2024-10-16 21:37:01.272
线程id=15 当前时间=2024-10-16 21:37:06.285
主线程结束, 当前时间=2024-10-16 21:37:10.224
【3.4】取消调度器与调度任务
1)取消调度器执行的方法:
- 调用 Timer.cancel()方法取消调度器; 若调度器有任务运行,则不结束; 若调度器没有任务运行,则可以结束;
2)取消调度任务的方法:
- 方法1: 在TimerTask内部run方法执行cancel()方法,取消当前调度任务执行;
- 方法2: 在TimerTask内部run()方法中强行终止当前线程执行;
【3.4.1】 在run方法里取消调度任务
【CancelRepeatableTimerTaskInsideRun】
public class CancelRepeatableTimerTaskInsideRun {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
cancel(); // 取消当前调度任务 (本来是重复调度,结果仅执行1次)
}
};
// 通过jdk 定时器执行调度任务
Timer timer = new Timer();
System.out.println("当前时间=" + BusiDatetimeUtils.getNowText());
// 在给定时间点(当前时间加5秒的时间)执行调度任务,间隔2s重复执行
timer.schedule(timerTask, BusiDatetimeUtils.timeAfterSecond(5), 2000);
System.out.println("after timer.schedule()新增调度任务之后");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 取消调度器执行(主线程结束)
timer.cancel();
System.out.println("主线程结束, 当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【执行效果】
当前时间=2024-10-16 21:48:59.496
after timer.schedule()新增调度任务之后
线程id=15 当前时间=2024-10-16 21:49:04.501
主线程结束, 当前时间=2024-10-16 21:49:09.510
【3.5】定时器Timer与调度线程池对比
【3.5.1】调度线程池执行调度任务回顾
【RepeatableTaskExecuteBySchedulerThreadPoolMain】使用调度线程池执行重复任务
public class RepeatableTaskExecuteBySchedulerThreadPoolMain {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1); // 不要这样用,本文仅演示
Runnable task = new Runnable() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
}
};
// 重复任务,间隔2s执行1次
scheduledExecutorService.scheduleAtFixedRate(task, 0, 2000, TimeUnit.MILLISECONDS);
}
}
【执行效果】
线程id=15 当前时间=2024-10-16 22:03:46.625
线程id=15 当前时间=2024-10-16 22:03:48.580
线程id=15 当前时间=2024-10-16 22:03:50.579
线程id=15 当前时间=2024-10-16 22:03:52.578
【注意】
实际开发过程中,不要使用 Executors.newScheduledThreadPool(1); 如下面代码所示,该方法初始化的最大线程个数无限大,这是有问题的; 线程个数无限大,会导致cpu时间片被耗尽,切换不到(或无法及时切换到)正常业务逻辑的线程上,导致系统假死 ;
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue());
}
【3.5.2】定时器与调度线程池ScheduledExecutorService对比
1)定时器Timer与调度线程池ScheduledExecutorService对比 :
- 定时器Timer对系统时钟变化非常敏感;而调度线程池不会;
- 定时器Timer只有1个执行线程; 而调度线程池可以配置多个;
- TimerTask内部run方法执行抛出运行时异常,则当前线程被杀死,导致后续任务不执行;而调度线程池中的线程抛出异常,仅运行中的任务被取消,但线程还存在,即其他任务还是可以被执行;
2)模拟TimerTask的run方法内部抛出异常
public class RepeatableTimerTaskExecuteThrowException {
public static void main(String[] args) {
TimerTask timerTask = new TimerTask() {
@Override
public void run() {
System.out.printf("线程id=%s 当前时间=%s \n", Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
throw new RuntimeException("mock runtime exception");
}
};
// 通过jdk 定时器执行调度任务
Timer timer = new Timer();
System.out.println("当前时间=" + BusiDatetimeUtils.getNowText());
// 在给定时间点(当前时间加5秒的时间)执行调度任务,间隔2s重复执行
timer.schedule(timerTask, BusiDatetimeUtils.timeAfterSecond(5), 2000);
System.out.println("after timer.schedule()新增调度任务之后");
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 取消调度器执行(主线程结束)
timer.cancel();
System.out.println("主线程结束, 当前时间=" + BusiDatetimeUtils.getNowText());
}
}
【执行效果】 显然,若TimerTask内部run方法抛出运行时异常,则整个定时器执行完成(定时器线程被杀死)
当前时间=2024-10-16 22:12:37.801
after timer.schedule()新增调度任务之后
线程id=15 当前时间=2024-10-16 22:12:42.803
Exception in thread "Timer-0" java.lang.RuntimeException: mock runtime exception
at com.tom.springnote.chapter31schedule.origintimer.RepeatableTimerTaskExecuteThrowException$1.run(RepeatableTimerTaskExecuteThrowException.java:22)
at java.base/java.util.TimerThread.mainLoop(Timer.java:566)
at java.base/java.util.TimerThread.run(Timer.java:516)
主线程结束, 当前时间=2024-10-16 22:12:47.806
3)模拟调度线程池ScheduledExecutorService的run方法内部抛出异常
【RepeatableTaskExecuteBySchedulerThreadPoolThrowExceptionMain】
public class RepeatableTaskExecuteBySchedulerThreadPoolThrowExceptionMain {
static class BusiTask implements Runnable {
private String flag;
public BusiTask(String flag) {
this.flag = flag;
}
@Override
public void run() {
System.out.printf("flag=%s, 线程id=%s 当前时间=%s \n", flag, Thread.currentThread().getId(), BusiDatetimeUtils.getNowText());
if (Objects.equals("exception", flag)) {
throw new RuntimeException("mock runtime exception");
}
}
}
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// 重复任务,间隔2s执行1次
scheduledExecutorService.scheduleAtFixedRate(new BusiTask("exception"), 0, 2000, TimeUnit.MILLISECONDS);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
// 第2次添加任务
scheduledExecutorService.scheduleAtFixedRate(new BusiTask("zhangsan"), 0, 2000, TimeUnit.MILLISECONDS);
}
}
【执行效果】 显然,即便抛出异常, 我们还是可以向调度线程池添加调度任务;
flag=exception, 线程id=15 当前时间=2024-10-16 22:25:34.802
flag=zhangsan, 线程id=15 当前时间=2024-10-16 22:25:36.750
flag=zhangsan, 线程id=15 当前时间=2024-10-16 22:25:38.756
flag=zhangsan, 线程id=15 当前时间=2024-10-16 22:25:40.764