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 {
    @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 


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/896460.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

查找与排序-选择排序

选择排序也是基于“比较”和“交换”两种操作来实现的排序方法 。 每一趟排序在待排序序列中选择关键字最小&#xff08;或最大&#xff09;的数据元素加入到排好序的序列前&#xff08;或后&#xff09;&#xff0c;直至所有元素排完为止。 一、简单选择排序 1.简单…

2024产品管理新风向:项目管理软件不懂敏捷开发?

一、产品管理与敏捷开发的紧密关联 产品管理和敏捷开发之间存在着紧密的关联&#xff0c;二者相互促进&#xff0c;共同为企业创造价值。 &#xff08;一&#xff09;敏捷开发为产品管理带来的优势 敏捷开发能够极大地加快产品上市速度。在传统的开发模式下&#xff0c;产品…

SAP 关于在交货单进行定价条件的确定简介

SAP 关于在交货单进行定价条件的确定简介 业务场景前台操作1、创建交货单2、创建交货单3、创建发票系统配置1、定义条件类型2、定义并分配定价过程3、定义交货的定价过程确定4、维护开票凭证的复制控制SAP交货单定价是针对销售交货单的价格计算过程,通常包括基本价格、折扣、附…

Java读取PDF后做知识库问答_SpringAI实现

​​​​​​​​​​​​​​ 核心思路&#xff1a; 简单来说&#xff0c;就是把PDF文件读取并向量化&#xff0c;然后放到向量存储里面&#xff0c;再通过大模型&#xff0c;来实现问答。 RAG&#xff08;检索增强生成&#xff09;介绍&#xff1a; 检索增强生成&#x…

数据结构——树、二叉树和森林间的转换

前言 介绍 &#x1f343;数据结构专区&#xff1a;数据结构 参考 该部分知识参考于《数据结构&#xff08;C语言版 第2版&#xff09;》129~130页 &#x1f308;每一个清晨&#xff0c;都是世界对你说的最温柔的早安&#xff1a;ૢ(≧▽≦)و✨ 目录 前言 1、基础知识 2…

Qml-Button的使用

Qml-Button的使用 Button属性 Button的继承关系&#xff1a; Button – AbstractButton – Control – Item; Button的属性主要继承于AbstractButton。AbstractButton属性主要如下&#xff1a; a.action:是一个Action类型属性&#xff0c;与QAction类似&#xff0c;用于提供快…

【论文解读系列】EdgeNAT: 高效边缘检测的 Transformer

代码&#xff1a; https://github.com/jhjie/edgenat 论文&#xff1a; https://arxiv.org/abs/2408.10527v1 论文 EdgeNAT: Transformer for Efficient Edge Detection 介绍了一种名为EdgeNAT的基于Transformer的边缘检测方法。 1. 背景与动机 EdgeNAT预测结果示例。(a, b)…

c语言基础程序——经典100道实例。

c语言基础程序——经典100道实例 001&#xff0c; 组无重复数字的数002&#xff0c;企业发放的奖金根据利润提成003&#xff0c;完全平方数004&#xff0c;判断当天是这一年的第几天005&#xff0c;三个数由小到大输出006&#xff0c;输出字母C图案007&#xff0c;特殊图案008&…

【Petri网导论学习笔记】Petri网导论入门学习(七) —— 1.5 并发与冲突

导航 1.5 并发与冲突1.5.1 并发定义 1.14定义 1.15 1.5.2 冲突定义 1.17 1.5.3 一般Petri网系统中的并发与冲突定义 1.18一般网系统中无冲撞概念阻塞&#xff08;有容量函数K的P/T系统&#xff0c;类似于冲撞&#xff09;一般Petri网中并发与冲突共存情况 1.5 并发与冲突 Petr…

lstm基础知识

lstm前言 LSTM(Long short-term memory)通过刻意的设计来避免长期依赖问题&#xff0c;是一种特殊的RNN。长时间记住信息实际上是 LSTM 的默认行为&#xff0c;而不是需要努力学习的东西&#xff01; 在标准的RNN中&#xff0c;这个重复模块具有非常简单的结构&#xff0c;例…

路由器原理和静态路由配置

一、路由器的工作原理 根据路由表转发数据 接收数据包→查看目的地址→与路由表进行匹配找到转发端口→转发到该端口 二、路由表的形成 它是路由器中维护的路由条目的集合&#xff0c;路由器根据路由表做路径选择&#xff0c;里面记录了网段ip地址和对应下一跳接口的接口号。…

【C语言备课课件】(下)指针pointer

目录 定义type *var_name;初始化int *p &a; // p指向变量a的地址 空指针NULL,野指针&#xff0c;指针悬挂 解引用指针的算术运算指针与数组 数组名—首指针二维数组指针 行指针列指针 多级指针&#xff08;进阶&#xff09;数组指针,指针数组&#xff08;进阶&#xff09…

如何利用 Python抓取网页数据 其他方式抓取网页数据列举

在 Python 中可以使用多种方法抓取网页数据&#xff0c;以下是一种常见的方法&#xff0c;使用requests和BeautifulSoup库。 一、安装所需库 在命令提示符或终端中执行以下命令安装requests和BeautifulSoup库&#xff1a; pip install requests pip install beautifulsoup4二…

python——类

问&#xff1a;小编为什么突然开始发python&#xff1f;难道C语言你不行了&#xff1f; 废话少说&#xff0c;让我们进入python中的类的学习&#xff01;&#xff01; &#xff08;一&#xff09;基本知识 &#xff08;1&#xff09;掌握类的概念 1、类的定义&#xff1a; 即…

python安装transformer教程

本章教程,记录在Windows中如何使用python安装transformer。 一、安装依赖 pip install transformers推荐使用国内镜像源,速度会快很多。 二、测试代码 from transformers import pipeline# 加载一个文本生成模型 text_generator = pipe

LCWLAN设备的实际使用案例

我们的LCWLAN设备在实际使用中以裸板的形式放在客户的智能总线控制器中&#xff0c;客户的 智能总线刀片灯&#xff0c;柔性灯货架&#xff0c;柔性感应钢网柜以及智能电子料架等设备都是接到总线控制 器中&#xff0c;然后总控制器通过CAN总线和我们的LCWLAN设备连接&#xff…

Linux DEADLINE调度算法详解

介绍 在实时系统中&#xff0c;调度算法的选择对于任务的及时执行至关重要。为了满足实时性需求&#xff0c;Linux内核引入了不同的调度算法&#xff0c;其中 DEADLINE 调度算法是为硬实时任务而设计的。DEADLINE 调度算法的目标是在多任务的情况下确保任务在其指定的最后期限…

Cpp::STL—容器适配器Stack和Queue的讲解和模拟实现(15)

文章目录 前言一、适配器模式概念分类 二、Stack核心作用代码实现 三、Queue核心作用代码实现 四、deque双端队列貌似兼收并蓄&#xff1f;实则也难以兼得~ 总结 前言 适配器也是STL六大组件之一&#xff0c;请跟我一起领悟它的智慧&#xff01;   正文开始&#xff01; 一、…

如何实现简单的 WinCC 项目分屏?

说明&#xff1a; 本文主要介绍了在不使用分屏器的情况下&#xff0c;通过 WinCC 项目中的设置&#xff0c;实现简单的分屏操作。两台显示器分别显示不同的 WinCC 画面&#xff0c;独自操作&#xff0c;互不影响。 试验环境 &#xff1a; 本文试验时所用硬件及软件环境…

案例分享—国外优秀UI设计作品赏析

国外UI界面设计之所以出色&#xff0c;首要原因在于其注重用户体验。设计师们深入洞察用户需求&#xff0c;通过细致的用户调研和数据分析&#xff0c;确保界面布局、色彩搭配及交互方式都能贴合用户习惯&#xff0c;从而提供流畅、直观的操作体验&#xff0c;增强用户满意度和…