当前位置: 首页 > news >正文

spring揭秘31-spring任务调度01-spring集成Quartz及JDKTimer定时器

文章目录

  • 【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组件清单

  1. Job: 抽象调度任务(作业);
  2. JobDetail:封装job的详细信息,如job名称,组名称,job的class,JobDataMap等;
  3. Trigger:触发器,抽象job触发规则(如执行开始时间,结束时间,执行频率);
  4. Scheduler:调度器; 使用Trigger定义的规则执行job;
  5. JobBuilder:Job生成器;用于创建Job实例;
  6. TriggerBuilder:Trigger生成器;用于创建Trigger实例;
  7. JobExecutionContext:Job运行时上下文;
  8. 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 {@Overridepublic 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 {@Overridepublic 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 {@Overridepublic 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 {@Overrideprotected 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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 exceptionat 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;}@Overridepublic 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 


相关文章:

spring揭秘31-spring任务调度01-spring集成Quartz及JDKTimer定时器

文章目录 【README】【1】Quartz任务调度框架【1.1】Job调度任务【1.2】任务调度触发器Trigger【1.3】\*Quartz框架执行调度任务代码实践【1.3.1】硬编码执行Quartz调度任务【1.3.2】基于生产者模式执行quartz调度任务&#xff08;推荐&#xff09; 【2】spring集成Quartz【2.1…...

Kafka之资源容量评估

编写目的意义 应用场景为如果有租户需要部署kafka集群&#xff0c;并给出业务压力&#xff0c;根据业务评估kafka资源情况&#xff0c;如cpu 磁盘 内存 带宽等维度。为业务解决因资源过小故障和新业务部署提供了参考和计算方法&#xff0c;减少后续的维护成本 资源容量评估 …...

深度学习神经网络的7大分类

深度学习中的神经网络可通过其结构和功能分为多种类型&#xff0c;每种都针对特定的数据特征和应用场景进行了优化。 深度学习7大神经网络如下&#xff1a; 01 前馈神经网络&#xff08;Feedforward Neural Networks, FNN&#xff09;&#xff1a; 这是最基本的神经网络形式…...

【DNF mysql8.0安装】DNF安装MySQL服务器教程

在基于Red Hat的Linux发行版中&#xff0c;如CentOS或Fedora&#xff0c;DNF&#xff08;Dandified Yum&#xff09;是包管理器&#xff0c;用于安装、更新和卸载软件包。以下是使用DNF安装MySQL服务器&#xff08;也称为MySQL Community Server&#xff09;的步骤&#xff1a;…...

决策树与随机森林在分类问题中的应用

决策树与随机森林在分类问题中的应用 分类问题是机器学习中的重要任务之一&#xff0c;它涉及将数据划分到预定义的类别中。在分类问题中&#xff0c;决策树与随机森林模型被广泛应用&#xff0c;凭借其直观性、强大的预测能力和稳定的泛化性能&#xff0c;成为了机器学习的经…...

Dmitri Shuralyov的全职开源之旅

本篇内容是根据2017年7月份Full-time Open Source 音频录制内容的整理与翻译 Dmitri Shuralyov 加入节目&#xff0c;谈论作为开源的全职贡献者、开发开发人员工具以及其他有趣的 Go 项目和新闻。 过程中为符合中文惯用表达有适当删改, 版权归原作者所有. Erik St. Martin: 欢迎…...

基于LSTM-Transformer混合模型实现股票价格多变量时序预测(PyTorch版)

前言 系列专栏:【深度学习&#xff1a;算法项目实战】✨︎ 涉及医疗健康、财经金融、商业零售、食品饮料、运动健身、交通运输、环境科学、社交媒体以及文本和图像处理等诸多领域&#xff0c;讨论了各种复杂的深度神经网络思想&#xff0c;如卷积神经网络、循环神经网络、生成对…...

创建TaskPool任务组

实现任务的函数需要使用装饰器Concurrent标注&#xff0c;且仅支持在.ets文件中使用。 方法&#xff1a; taskpool.execute(任务名,执行权重优先级) import { taskpool } from kit.ArkTS//Concurrent 只能修饰全局函数 Concurrent async function getData(params1: string,…...

一文1800字从0到1浅谈web性能测试!

什么是性能测试&#xff1f; web性能应该注意些什么&#xff1f; 性能测试&#xff0c;简而言之就是模仿用户对一个系统进行大批量的操作&#xff0c;得出系统各项性能指标和性能瓶颈&#xff0c;并从中发现存在的问题&#xff0c;通过多方协助调优的过程。而web端的性能测试…...

计算机网络基础(1)

个人主页&#xff1a;C忠实粉丝 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 C忠实粉丝 原创 计算机网络基础 收录于专栏【计算机网络】 本专栏旨在分享学习计算机网络的一点学习笔记&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 1. 计算机网…...

GNU/Linux - 宏处理工具M4

GNU M4 M4 "Macro Processor, Version 4". 1, Introduction to GNU M4 GNU M4 是传统 Unix 宏处理器的实现。它主要与 SVR4 兼容&#xff0c;但也有一些扩展功能&#xff08;例如&#xff0c;处理超过 9 个位置参数的宏命令&#xff09;。GNU M4 还内置了包含文件、…...

Oracle权限安全管理

实验内容 本次实验先使用system用户连接 温馨提示&#xff1a;题目要求切换账户登录的时候自己记得切换&#xff0c;本文章只提供相应的SQL语句 在表空间BOOKTBS1&#xff08;实验4第1题已创建&#xff09;创建一张表BOOKS,其字段如下&#xff1a;&#xff1a; SQL> create…...

C++笔记之静态多态和动态多态

C++笔记之静态多态和动态多态 code review! 在C++中,多态(Polymorphism)是面向对象编程的一个核心概念,允许对象以多种形式存在。多态性主要分为静态多态(Static Polymorphism)和动态多态(Dynamic Polymorphism)。下面将详细解释这两种多态及其在C++中的实现方式、优缺…...

Axure RP电商系统商城PC+app+后台买家卖端高保真原型模板及元件库

AxureRP电商商城PCapp后台买家卖端高保真原型模板本套包含三份原型图素材 APP买家端原型简介&#xff1a; 包含了用户中心、会员成长、优惠券、积分、互动社区、运营推广、内容推荐、商品展示、订单流程、订单管理、售后及服务等完整的电商体系功能架构和业务流程。 本模板由…...

RTX3070的yolo训练模型迁移到NVIDIA JETSON XAVIER NX 上的踩坑经验,时机部署避雷点

NVIDIA JETSON XAVIER NX 的yolo环境部署 首先为了保证yolo的权重模型pt文件可以顺利迁移过去&#xff0c;要保证torch和cuda的版本一致 如何在NX上安装torch? 1.用 jtop工具 实时查看和控制板子状态 安装: sudo -H pip3 install jetson-stats使用: sudo jtop 在这里是为…...

带你学习如何编写一篇API详设文档以及给新人提点建议

文章目录 前言先认清一个问题详设文档如何写先看文档脉络详设文档分析需求背景方案概述API定义安全设计性能设计缓存与数据库 总结 前言 这篇文章带读者了解软件开发项目中一个需求的开发详设文档主要包括哪些内容&#xff0c;其中重点会给读者分析API设计的规范&#xff0c;相…...

【Python爬虫实战】正则:多字符匹配、开头与结尾定位、分组技术详解

&#x1f308;个人主页&#xff1a;https://blog.csdn.net/2401_86688088?typeblog &#x1f525; 系列专栏&#xff1a;https://blog.csdn.net/2401_86688088/category_12797772.html 目录 前言 一、匹配多个字符 &#xff08;一&#xff09;匹配任意多个字符 &#xff0…...

DOIP协议介绍-1

1.DOIP中的GID和EID是什么&#xff1f; 在DOIP&#xff08;Diagnostics over IP&#xff09;中&#xff0c;GID&#xff08;Group Identification&#xff09;和EID&#xff08;Entity Identification&#xff09;是两个重要的标识符&#xff0c;它们各自承担着不同的角色和功…...

探索Python中的多线程与多进程

在Python编程中&#xff0c;多线程和多进程是两个重要的概念&#xff0c;它们被用来提高程序的执行效率。本文将深入探讨这两个概念&#xff0c;并对比它们在Python中的实现方式。 一、多线程 多线程是一种并发执行的程序设计方法。在Python中&#xff0c;我们可以使用thread…...

paypal php 实现详细攻略

一、准备工作 登录 https://www.paypal.com/ 注册一个主账号&#xff08;选择个人账号、企业账后都可&#xff09; 申请完成后登录https://developer.paypal.com/ 在后台右侧菜地点击“Accounts”&#xff0c;可以看到系统自动给分配的两个沙箱环境的账号。类型为Personal是个人…...

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…...

手游刚开服就被攻击怎么办?如何防御DDoS?

开服初期是手游最脆弱的阶段&#xff0c;极易成为DDoS攻击的目标。一旦遭遇攻击&#xff0c;可能导致服务器瘫痪、玩家流失&#xff0c;甚至造成巨大经济损失。本文为开发者提供一套简洁有效的应急与防御方案&#xff0c;帮助快速应对并构建长期防护体系。 一、遭遇攻击的紧急应…...

css实现圆环展示百分比,根据值动态展示所占比例

代码如下 <view class""><view class"circle-chart"><view v-if"!!num" class"pie-item" :style"{background: conic-gradient(var(--one-color) 0%,#E9E6F1 ${num}%),}"></view><view v-else …...

微软PowerBI考试 PL300-选择 Power BI 模型框架【附练习数据】

微软PowerBI考试 PL300-选择 Power BI 模型框架 20 多年来&#xff0c;Microsoft 持续对企业商业智能 (BI) 进行大量投资。 Azure Analysis Services (AAS) 和 SQL Server Analysis Services (SSAS) 基于无数企业使用的成熟的 BI 数据建模技术。 同样的技术也是 Power BI 数据…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

土地利用/土地覆盖遥感解译与基于CLUE模型未来变化情景预测;从基础到高级,涵盖ArcGIS数据处理、ENVI遥感解译与CLUE模型情景模拟等

&#x1f50d; 土地利用/土地覆盖数据是生态、环境和气象等诸多领域模型的关键输入参数。通过遥感影像解译技术&#xff0c;可以精准获取历史或当前任何一个区域的土地利用/土地覆盖情况。这些数据不仅能够用于评估区域生态环境的变化趋势&#xff0c;还能有效评价重大生态工程…...

LLM基础1_语言模型如何处理文本

基于GitHub项目&#xff1a;https://github.com/datawhalechina/llms-from-scratch-cn 工具介绍 tiktoken&#xff1a;OpenAI开发的专业"分词器" torch&#xff1a;Facebook开发的强力计算引擎&#xff0c;相当于超级计算器 理解词嵌入&#xff1a;给词语画"…...

C# 求圆面积的程序(Program to find area of a circle)

给定半径r&#xff0c;求圆的面积。圆的面积应精确到小数点后5位。 例子&#xff1a; 输入&#xff1a;r 5 输出&#xff1a;78.53982 解释&#xff1a;由于面积 PI * r * r 3.14159265358979323846 * 5 * 5 78.53982&#xff0c;因为我们只保留小数点后 5 位数字。 输…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

算法笔记2

1.字符串拼接最好用StringBuilder&#xff0c;不用String 2.创建List<>类型的数组并创建内存 List arr[] new ArrayList[26]; Arrays.setAll(arr, i -> new ArrayList<>()); 3.去掉首尾空格...