Quartz知识点总结
简单说明
简单的定时任务使用Timer或者ScheduledExecutorService
quartz支持复杂的定时执行功能。支持ram存储(内存存储)和持久化存储。quartz有分布式和集群能力
简单使用
- 获取任务调度器Schedule。任务调度器可以管理任务。
- 创建任务实例。使用JobBuilder(任务类.class)创建,会返回一个JobDetail。任务是一个实现了Job,并实现execute方法的类而已,execute方法记录要执行的事情。
- 创建触发器Trigger。Trigger是用来指定触发策略的,包括什么时候开始执行、循环多少次、多久执行一次呀,等等。支持链式编程。
- 把任务和触发器告诉任务调度器,使用scheduleJob方法来完成。
- 使用任务调度器启动任务
- 使用任务调度器关闭任务
例子:
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class SimpleJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("SimpleJob is executing at: " + new java.util.Date());}
}
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class QuartzExample {public static void main(String[] args) {try {// 1. 获取任务调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 2. 创建任务实例JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("simpleJob", "group1").build();// 3. 创建触发器Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10) // 每10秒执行一次.repeatForever()) // 无限重复.build();// 4. 把任务和触发器告诉任务调度器scheduler.scheduleJob(job, trigger);// 5. 使用任务调度器启动任务scheduler.start();// 让任务运行一段时间Thread.sleep(60000); // 60秒// 6. 使用任务调度器关闭任务scheduler.shutdown();} catch (SchedulerException | InterruptedException e) {e.printStackTrace();}}
}
使用了 Builder 模式(建造者模式):
JobDetail job = JobBuilder.newJob(SimpleJob.class)
.withIdentity("simpleJob", "group1")
.build();
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("simpleTrigger", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(10).repeatForever()).build();
Quartz基本的实现原理
- job和jobdetail的关系是什么?
Job(任务)是一个接口,表示一个具体的任务。你需要实现这个接口,并在 execute 方法中定义任务的具体逻辑。Job 只关注任务的执行逻辑,即 做什么。
JobDetail(任务详情)JobDetail 是一个类,用于描述任务的详细信息。它包含了任务的元数据,例如任务类、任务名称、任务组、任务数据等。JobDetail 关注任务的 元信息,即 谁来做 和 怎么做。
JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("simpleJob", "group1").build();
这里 JobDetail 描述了任务的详细信息:
- 任务类是 SimpleJob.class。
- 任务名称是 “simpleJob”。
- 任务组是 “group1”。
总结:
- Job:定义任务的具体逻辑(做什么)。
- JobDetail:描述任务的元信息(谁来做、怎么做)。
- 关系:JobDetail 是 Job 的包装器,包含了 Job 的类信息和任务的额外信息。
- 设计目的:解耦任务逻辑和任务元信息,提高灵活性和可扩展性。
- 原理图:
- Job、JobDetail和Trigger的关系
Trigger:
- 一个 Trigger 只能绑定一个 JobDetail。
JobDetail:
- 一个 JobDetail 可以被多个 Trigger 绑定。相当于是一个Job信息可以被多次使用。
Job:
- 一个 Job 类可以创建多个 JobDetail 对象。相当于是可以允许多个JobDetail使用一个Job的execute逻辑。
- 默认情况下,Quartz 每次触发器触发时都会创建一个新的 Job 实例,再去执行execute方法。(这样可以线程安全)
例子:
package com.example;import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import java.util.*;
import static org.quartz.JobBuilder.newJob;public class SimpleTriggerMisfireExample {public static void main(String[] args) throws SchedulerException {SchedulerFactory schedulerFactory = new StdSchedulerFactory();Scheduler scheduler = schedulerFactory.getScheduler();JobDetail job = newJob(MyJob.class).withIdentity("myJob", "group1").storeDurably().build();Trigger trigger1 = TriggerBuilder.newTrigger().withIdentity("trigger1", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5).repeatForever()).build();Trigger trigger2 = TriggerBuilder.newTrigger().withIdentity("trigger2", "group1").startNow().withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever()).build();Set<Trigger> triggers = new HashSet<>();triggers.add(trigger1);triggers.add(trigger2);Map<JobDetail, Set<? extends Trigger>> triggersAndJobs = new HashMap<>();triggersAndJobs.put(job, triggers);scheduler.scheduleJobs(triggersAndJobs, false);System.out.println("调度器启动时间: " + new Date());scheduler.start();try {Thread.sleep(60000); // 等待 60 秒} catch (InterruptedException e) {e.printStackTrace();}scheduler.shutdown();}public static class MyJob implements Job {@Overridepublic void execute(JobExecutionContext context) {Trigger trigger = context.getTrigger();System.out.println("任务执行开始: " + new Date() +", Trigger: " + trigger.getKey() +", 触发时间: " + trigger.getPreviousFireTime());try {Thread.sleep(3000); // 模拟任务执行耗时 3 秒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("任务执行结束: " + new Date());}}
}
执行结果:
D:\jdk1.8.0_172\bin\java.exe "-javaagent:D:\IDEA\IntelliJ IDEA 2021.3.2\lib\idea_rt.jar=55136:D:\IDEA\IntelliJ IDEA 2021.3.2\bin" -Dfile.encoding=UTF-8 -classpath D:\jdk1.8.0_172\jre\lib\charsets.jar;D:\jdk1.8.0_172\jre\lib\deploy.jar;D:\jdk1.8.0_172\jre\lib\ext\access-bridge-64.jar;D:\jdk1.8.0_172\jre\lib\ext\cldrdata.jar;D:\jdk1.8.0_172\jre\lib\ext\dnsns.jar;D:\jdk1.8.0_172\jre\lib\ext\jaccess.jar;D:\jdk1.8.0_172\jre\lib\ext\jfxrt.jar;D:\jdk1.8.0_172\jre\lib\ext\localedata.jar;D:\jdk1.8.0_172\jre\lib\ext\nashorn.jar;D:\jdk1.8.0_172\jre\lib\ext\sunec.jar;D:\jdk1.8.0_172\jre\lib\ext\sunjce_provider.jar;D:\jdk1.8.0_172\jre\lib\ext\sunmscapi.jar;D:\jdk1.8.0_172\jre\lib\ext\sunpkcs11.jar;D:\jdk1.8.0_172\jre\lib\ext\zipfs.jar;D:\jdk1.8.0_172\jre\lib\javaws.jar;D:\jdk1.8.0_172\jre\lib\jce.jar;D:\jdk1.8.0_172\jre\lib\jfr.jar;D:\jdk1.8.0_172\jre\lib\jfxswt.jar;D:\jdk1.8.0_172\jre\lib\jsse.jar;D:\jdk1.8.0_172\jre\lib\management-agent.jar;D:\jdk1.8.0_172\jre\lib\plugin.jar;D:\jdk1.8.0_172\jre\lib\resources.jar;D:\jdk1.8.0_172\jre\lib\rt.jar;E:\develop\spring\target\classes;C:\Users\Administrator\.m2\repository\org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar;C:\Users\Administrator\.m2\repository\com\mchange\c3p0\0.9.5.4\c3p0-0.9.5.4.jar;C:\Users\Administrator\.m2\repository\com\mchange\mchange-commons-java\0.2.15\mchange-commons-java-0.2.15.jar;C:\Users\Administrator\.m2\repository\com\zaxxer\HikariCP-java7\2.4.13\HikariCP-java7-2.4.13.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-api\1.7.36\slf4j-api-1.7.36.jar;C:\Users\Administrator\.m2\repository\org\slf4j\slf4j-simple\1.7.36\slf4j-simple-1.7.36.jar com.example.SimpleTriggerMisfireExample
[main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
[main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
[main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
[main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
[main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
[main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.NOT STARTED.Currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.[main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
[main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
调度器启动时间: Wed Mar 19 19:29:33 CST 2025
任务执行开始: Wed Mar 19 19:29:33 CST 2025, Trigger: group1.trigger1, 触发时间: Wed Mar 19 19:29:33 CST 2025
[main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
任务执行开始: Wed Mar 19 19:29:33 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:33 CST 2025
任务执行开始: Wed Mar 19 19:29:36 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:36 CST 2025
任务执行结束: Wed Mar 19 19:29:36 CST 2025
任务执行结束: Wed Mar 19 19:29:36 CST 2025
任务执行开始: Wed Mar 19 19:29:38 CST 2025, Trigger: group1.trigger1, 触发时间: Wed Mar 19 19:29:38 CST 2025
任务执行开始: Wed Mar 19 19:29:39 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:39 CST 2025
任务执行结束: Wed Mar 19 19:29:39 CST 2025
任务执行结束: Wed Mar 19 19:29:41 CST 2025
任务执行开始: Wed Mar 19 19:29:42 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:42 CST 2025
任务执行结束: Wed Mar 19 19:29:42 CST 2025
任务执行开始: Wed Mar 19 19:29:43 CST 2025, Trigger: group1.trigger1, 触发时间: Wed Mar 19 19:29:43 CST 2025
任务执行结束: Wed Mar 19 19:29:45 CST 2025
任务执行开始: Wed Mar 19 19:29:45 CST 2025, Trigger: group1.trigger2, 触发时间: Wed Mar 19 19:29:45 CST 2025
任务执行结束: Wed Mar 19 19:29:46 CST 2025进程已结束,退出代码130
Scheduler
只要知道Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();就行
Trigger
Trigger 的触发器中主要是下面两种比较常用:
- SimpleTrigger:适用于单次或固定间隔重复执行的任务。
- CronTrigger:适用于复杂调度规则的任务。他也能和SimpleTrigger一样,指定开始执行和结束执行的时间。
SimpleTrigger适合场景:
- 定时任务:比如指定晚上12点去导出订单
- 延时任务:比如,当用户提交订单后,启动任务,并且延时30秒钟后检测订单状态,如果没有支付就取消订单并释放库存
- 循环任务:可以无限循环、可以指定循环次数、可以指定循环到某个时间停止
具体使用可以让GBT给出简单使用案例。
CronTrigger适合场景:
- 适合需要复杂调度规则的任务(例如每天、每周、每月执行、每个星期几执行、第几周执行)。
- 需要精确控制执行时间的任务。
Cron 表达式由 6 或 7 个字段组成(各字段使用空格分割,最后一个年份是可选的),格式如下:
秒 分 时 日 月 周 年(可选)
cron表达式的规则:
- 秒(0-59):表示每分钟的秒数。
- 分(0-59):表示每小时的分钟数。
- 时(0-23):表示每天的小时数。
- 日(1-31):表示每月的日期。
- 月(1-12 或 JAN-DEC):表示每年的月份。使用月份的前3个字母也是可以表示月份的。
- 周(1-7 或 SUN-SAT):表示每周的星期几(1 表示周日,7 表示周六)。
- 年(可选,1970-2099):表示年份(可以省略)。省略表示对年份没有要求,任何年份都可以执行,如果指定年份,那么久只能在对应的年份才能执行。
Cron 表达式中可以使用以下符号来定义时间的范围和规则:
- 星号(*)
含义:表示“每”或“任意”。
示例:
0 * * * * ?:每分钟的第 0 秒执行(即每分钟执行一次)。
0 0 * * * ?:每小时的 0 分 0 秒执行(即每小时执行一次)。
- 问号(?)
含义:表示“无特定值”,它通常用于 “日”字段 或 “周”字段,以避免这两个字段之间的冲突。因为“日”和“周”字段是互斥的,不能同时指定具体的值,所以需要用 ? 来表示其中一个字段不指定具体值。
示例:
0 0 12 ? * MON:每周一的 12:00:00 执行。
0 0 0 1 * ?:每月 1 日的 00:00:00 执行。
- 逗号(,)
含义:表示“或”,用于列举多个值。
示例:
0 0 12,18 * * ?:每天的 12:00:00 和 18:00:00 执行。
0 0 0 1,15 * ?:每月 1 日和 15 日的 00:00:00 执行。
- 连字符(-)
含义:表示“范围”,用于定义一个连续的时间范围。
示例:
0 0 9-17 * * ?:每天 9:00:00 到 17:00:00,每小时执行一次。
0 0 0 1-5 * ?:每月 1 日到 5 日的 00:00:00 执行。
- 斜杠(/)
含义:表示“间隔”,用于定义时间间隔。
示例:
0 0/5 * * * ?:每 5 分钟执行一次(从 0 分钟开始)。
0 0 0/2 * * ?:每 2 小时执行一次(从 0 点开始)。
0 0 10/7 * * ?:从 10 点开始,每 7 个小时执行一次。过了范围后还是一样从10点开始的。
执行的时间如下:
- 2025-03-13 10:00:00
- 2025-03-13 17:00:00
- 2025-03-14 10:00:00
- 2025-03-14 17:00:00
- 2025-03-15 10:00:00
- 井号(#)
含义:表示“第几个”,用于指定某月的第几个星期几。
示例:
0 0 12 ? * 6#3:每月的第 3 个星期五的 12:00:00 执行。
0 0 12 ? * 2#1:每月的第 1 个星期一的 12:00:00 执行。
- 字母(L、W)
L:L加在时间上面,就是代表最后一个这个时间才会执行。比如,加在日上,就是最后一个满足条件的日才会执行。加在周上,就是最后一个满足条件的周才执行。允许使用在月、日、星期几上。如果在月、日、周上面写L,不写其他的,分别就是指满足条件的最后一个月、满足条件的最后一天、满足条件的星期六。
示例:
0 0 0 L * ?:每月的最后一天的 00:00:00 执行。
0 0 12 ? * 5L:每月的最后一个星期五的 12:00:00 执行。
0 0 12 L * ?:表示每月最后一天的12:00:00执行。
W:表示“工作日”(Weekday),用于“日”字段,指定离给定日期最近的工作日。
示例:
0 0 0 15W * ?:每月 15 日最近的工作日的 00:00:00 执行。
比如:当前是2025年3月12日
- 2025-03-14 00:00:00(周五)
- 2025-04-15 00:00:00(周二)
- 2025-05-15 00:00:00(周四)
- 2025-06-16 00:00:00(周一)
- 2025-07-15 00:00:00(周二)
例如:
- 0 0 12 * * ?:每天中午 12 点执行。
- 0 0/5 14 * * ?:每天下午 2 点到 2:55,每 5 分钟执行一次。
使用案例:创建一个任务在每天的 12:00:00 执行。
import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;public class SimpleJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {System.out.println("SimpleJob is executing at: " + new java.util.Date());}
}
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class CronTriggerExample {public static void main(String[] args) {try {// 1. 获取任务调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 2. 创建任务实例JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("cronJob", "group1").build();// 3. 创建 CronTriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("cronTrigger", "group1").withSchedule(CronScheduleBuilder.cronSchedule("0 0 12 * * ?")) // 每天中午 12:00:00 执行.build();// 4. 把任务和触发器告诉任务调度器scheduler.scheduleJob(job, trigger);// 5. 使用任务调度器启动任务scheduler.start();// 让任务运行一段时间Thread.sleep(60000); // 60秒// 6. 使用任务调度器关闭任务scheduler.shutdown();} catch (SchedulerException | InterruptedException e) {e.printStackTrace();}}
}
触发器的Misfire策略
misfire解释
Misfire(错过触发) 发生在触发器的计划时间已经过去,但由于某些原因(例如,调度器未启动、任务执行时间过长、系统负荷等),未能按预定时间执行触发器。这种情况通常是由于调度器(Scheduler)没有运行(可能是没有启动、暂停、关闭等原因)、线程池没有空闲的线程供调度器调度而产生的(调度器去执行任务的时候,其实是使用线程池中的线程去执行的)。如下图:
你可以理解为,Trigger创建时,你没有指定start就是指计划开始执行的时间是Trigger创建的时间,如果你指定了某个时间,就是计划了从你指定的开始时间开始什么时候去执行什么任务(注意,如果你创建trigger的时候,设置开始时间是过去的时间,那么你start这个Scheduler的时候,就可能就直接就会直接补偿了)。相当于是有一个计划表,如果当前时间超过了那个任务指定要执行的时间,但是又没有执行,那么就是misfire了,如果超过了,但是执行了那就是正常的。相当于在时间表上,打了✓,但是如果是misfire,那么就是相当于打×。Trigger会有补偿机制,会去补偿执行错过的任务,补偿机制是什么样的取决于你设置的misfire处理策略。
misfire发生的场景:
①:所有的工作线程都在忙碌,导致某些trigger得不到触发.(如:simplethreadpool 默认是10个工作线程,但我有15个trigger同时触发, 恰巧这10个trigger关联的job耗时都很长,剩下的5个trigger超过了等待时间仍然没有得到触发)
②:调度器(sheduler)中途挂了,某个时间又恢复了
③:设置的trigger的开始时间早于当前时间
Misfire 补偿
Quartz 为不同类型的触发器(如 SimpleTrigger 和 CronTrigger)提供了不同的 Misfire 处理策略。
我们主要使用的是SimpleTrigger 和 CronTrigger,所以我们只介绍这两个触发器的Misfire处理策略。
SimpleTrigger
略。这个我测试发现结果和很多博客、GBT的结果不一样,并且因为这个用的也没有很多,这里不记录了。主要了解CronTrigger 就行了。
CronTrigger
CronTrigger 提供了以下几种处理 Misfire 策略:
- withMisfireHandlingInstructionDoNothing:错过的不进行补偿,然后正常调度。
- withMisfireHandlingInstructionFireAndProceed:错过的全部合成一次,并且立即进行补偿(即时任务终止时间已经到达),然后正常调度。默认是这个。
- withMisfireHandlingInstructionIgnoreMisfires:错过的全部立即进行补偿(即时任务终止时间已经到达),然后正常调度
代码:
package com.example.springbootdemo.config;import com.example.springbootdemo.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;@Configuration
public class QuartzConfiguration {private String jobName="myjob";@Bean("myJobDetail")public JobDetail jobDetail(){return JobBuilder.newJob(MyJob.class).storeDurably().withIdentity(jobName).build();}@Beanpublic Trigger trigger(){String cronExpression="0 0/2 * * * ?";CronScheduleBuilder cronScheduleBuilder=CronScheduleBuilder.cronSchedule(cronExpression).withMisfireHandlingInstructionDoNothing();return TriggerBuilder.newTrigger().startAt(DateBuilder.todayAt(10,50,0)).withIdentity(jobName+"_trigger").withSchedule(cronScheduleBuilder).build();}@Beanpublic Scheduler scheduler(Trigger trigger, JobDetail jobDetail) throws SchedulerException {Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();scheduler.scheduleJob(jobDetail, trigger);return scheduler;}
}
package com.example.springbootdemo.job;import org.quartz.Job;
import org.quartz.JobDetail;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import java.text.SimpleDateFormat;
import java.util.Date;public class MyJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {// 获取任务关联的jobDetailJobDetail jobDetail = context.getJobDetail();// 获取任务的名字String jobName = jobDetail.getKey().getName();// 获取任务的本次执行时间Date fireTime = context.getFireTime();String executeTime = new SimpleDateFormat("yyyy-HW-dd HH:mm:ss").format(fireTime);// 输出任务的相关信息System.out.println("|---"+jobName+"---!");System.out.println("\t本次执行时间:"+executeTime);}
}
package com.example.springbootdemo.run;import org.quartz.Scheduler;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;// 在 Spring Boot 应用启动时,QuartzStartupRunner 的 run 方法会被自动调用。
// 在 run 方法中,scheduler.start() 会启动 Quartz 调度器。
// 一旦调度器启动,所有配置好的任务(Job)和触发器(Trigger)就会开始按照预定的时间表执行。
@Component
public class QuartzStartupRunner implements CommandLineRunner {private final Scheduler scheduler;public QuartzStartupRunner(Scheduler scheduler) {this.scheduler = scheduler;}@Overridepublic void run(String... args) throws Exception {scheduler.start();}
}
package com.example.springbootdemo;import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication
public class SpringbootDemoApplication {public static void main(String[] args) {SpringApplication.run(SpringbootDemoApplication.class, args);}}
Job、JobDetail和Scheduler
quartz中提供了一个Job接口,如果你想要有一个定时执行的任务,那么你就需要创建一个类,实现这个Job接口,并且实现其execute方法,execute方法中写要定时执行的逻辑。然后创建一个JobDetail,JobDetail创建的时候要指定Job实现类,你也可以指定一些其他的任务信息。然后创建一个Trigger,指定执行的时间。接着,把JobDetail和Trigger告诉调度器,让调度器去按你计划执行,这样就实现了一个定时执行的任务了。
Job只是记录做什么事情而已,不记录任何其他的,所以很单一。
Detail会绑定一个Job的class对象,相当于是把任务执行的逻辑告诉给JobDetail。当然JobDetail还会记录一些其他的任务信息,不只是记录任务的逻辑是什么,还记录其他的,比如任务叫什么名字,任务属于什么组,任务描述信息呀,等等。
JobDetail 只是一个描述 Job 的元数据对象,它不会直接调用 Job 的 execute 方法。也不会new一个Job实例。
真正会调用Job逻辑的东西其实是Scheduler,他会根据Trigger指定的时间去执行,即,到了该执行的时间,就会去创建Job,然后调用他的execute方法。默认情况下,Scheduler每次在Trigger指定的时间去执行Job的execute的时候,都是会创建一个新的Job对象去执行的。因为可能会有多个调度器会用一个Job的execute逻辑。
Job的状态
分类
Job 可以分为两种类型:有状态的 Job(Stateful Job) 和 无状态的 Job(Stateless Job)。它们的区别主要体现在任务执行过程中是否保留状态,以及是否支持并发执行。
- 无状态的 Job(Stateless Job)
无状态的 Job 是 Quartz 的默认行为。每次任务执行时,Quartz 都会创建一个新的 Job 实例,且不会保留任何状态。
特点:
- 每次执行都是独立的:每次触发器触发时,Quartz 都会创建一个新的 Job 实例,任务执行完成后,实例会被销毁。
- 支持并发执行:多个触发器可以同时触发同一个 Job,Quartz 会为每个触发器创建一个新的 Job 实例
- 不保留状态:JobDataMap 中的数据在任务执行后不会被保留。即使你在 execute 方法中修改了 JobDataMap,并且把修改后的值put进去,这些修改也不会影响下一次任务的执行。
适用场景:
- 任务是无状态的,不需要保留任何数据。
- 任务可以并发执行,且不需要考虑线程安全问题。
- 有状态的 Job(Stateful Job)
有状态的 Job 会在任务执行过程中保留状态,且不支持并发执行。Quartz 通过 @PersistJobDataAfterExecution 和 @DisallowConcurrentExecution 注解来实现有状态的 Job。
特点:
- 保留状态:任务执行过程中对 JobDataMap 的修改会被保留,并影响下一次任务的执行。
- 不支持并发执行:同一时间只能有一个触发器执行该 Job,其他触发器需要等待当前任务执行完成后才能执行。
- 单例行为:即使有多个触发器同时执行,Quartz 也会确保同一时间只有一个任务实例在执行。
适用场景:
- 任务需要保留状态(例如,记录任务的执行次数或中间结果)。
- 任务不能并发执行,需要确保线程安全。
例子:
@PersistJobDataAfterExecution // 保留 JobDataMap 的状态
@DisallowConcurrentExecution // 禁止并发执行
public class StatefulJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {JobDataMap dataMap = context.getJobDetail().getJobDataMap();int count = dataMap.getInt("count", 0);count++;dataMap.put("count", count); // 修改状态System.out.println("Stateful Job Executed! Count: " + count);}
}
如何选择无状态 Job 和有状态 Job?
- 如果你的任务是独立的,不需要保留任何状态,且可以并发执行,使用无状态 Job。
- 如果你的任务需要保留状态(例如,记录执行次数或中间结果),使用有状态 Job。
注意事项
- 有状态 Job 的性能:由于有状态 Job 不支持并发执行,可能会影响任务的执行效率。如果任务执行时间较长,可能会导致其他触发器等待。
- 线程安全:即使是有状态 Job,也需要确保 JobDataMap 中的数据类型是线程安全的(例如,使用 AtomicInteger 而不是普通的 int)。
- 状态持久化:如果使用 @PersistJobDataAfterExecution,JobDataMap 的状态会被持久化到数据库中(如果配置了持久化存储)。确保数据库连接和配置正确。
参数传递
Job的抽象方法execute的参数是JobExecutionContext类型的,他是一个非常重要的对象,它提供了任务执行时的上下文信息。很重要的一个作用就是,他可以接收Trigger和JobDetail传递的参数。
你在Trigger或者JobDetail中可以把一些数据保存到JobDataMap中,然后具体执行execute方法的时候,execute中可以通过JobExecutionContext去获取到对应的JobDataMap数据,进行你需要的逻辑。
例子:
package com.example;import com.example.job.MyJob;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;public class QuartzExample {public static void main(String[] args) throws SchedulerException {// 创建调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 启动调度器scheduler.start();// 定义 JobDetail,并设置 JobDataMapJobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("myJob", "group1").usingJobData("message", "Hello from JobDetail!") // JobDetail 中的参数.usingJobData("count", 0) // JobDetail 中的参数.build();// 定义 Trigger,并设置 JobDataMapTrigger trigger = TriggerBuilder.newTrigger().withIdentity("myTrigger", "group1").startNow().usingJobData("message", "Hello from Trigger!") // Trigger 中的参数.usingJobData("count", 100) // Trigger 中的参数.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(5) // 每 5 秒执行一次.repeatForever()).build();// 将 JobDetail 和 Trigger 注册到调度器中scheduler.scheduleJob(jobDetail, trigger);}
}
package com.example.job;import org.quartz.Job;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobDataMap;public class MyJob implements Job {@Overridepublic void execute(JobExecutionContext context) throws JobExecutionException {// 获取 JobDetail 中的 JobDataMapJobDataMap jobDetailDataMap = context.getJobDetail().getJobDataMap();// 获取 Trigger 中的 JobDataMapJobDataMap triggerDataMap = context.getTrigger().getJobDataMap();// 获取合并后的 JobDataMap(Trigger 中的参数会覆盖 JobDetail 中的同名参数)JobDataMap mergedDataMap = context.getMergedJobDataMap();// 从 JobDetail 中获取参数String jobDetailMessage = jobDetailDataMap.getString("message");int jobDetailCount = jobDetailDataMap.getInt("count");// 从 Trigger 中获取参数String triggerMessage = triggerDataMap.getString("message");int triggerCount = triggerDataMap.getInt("count");// 从合并后的 JobDataMap 中获取参数String mergedMessage = mergedDataMap.getString("message");int mergedCount = mergedDataMap.getInt("count");// 打印参数System.out.println("JobDetail Message: " + jobDetailMessage);System.out.println("JobDetail Count: " + jobDetailCount);System.out.println("Trigger Message: " + triggerMessage);System.out.println("Trigger Count: " + triggerCount);System.out.println("Merged Message: " + mergedMessage);System.out.println("Merged Count: " + mergedCount);// 更新计数(如果需要)mergedCount++;mergedDataMap.put("count", mergedCount); // 更新合并后的 JobDataMap}
}
输出:
"D:\1program file\Java\jdk1.8.0_231\bin\java.exe" "-javaagent:D:\Program Files\JetBrains\IntelliJ IDEA 2021.3.3\lib\idea_rt.jar=10177:D:\Program Files\JetBrains\IntelliJ IDEA 2021.3.3\bin" -Dfile.encoding=UTF-8 -classpath "D:\1program file\Java\jdk1.8.0_231\jre\lib\charsets.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\deploy.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\access-bridge-64.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\cldrdata.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\dnsns.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\jaccess.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\jfxrt.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\localedata.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\nashorn.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunec.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunjce_provider.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunmscapi.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\sunpkcs11.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\ext\zipfs.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\javaws.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jce.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jfr.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jfxswt.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\jsse.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\management-agent.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\plugin.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\resources.jar;D:\1program file\Java\jdk1.8.0_231\jre\lib\rt.jar;E:\SpringBootDemo1\target\classes;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-web\2.5.0\spring-boot-starter-web-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter\2.5.0\spring-boot-starter-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot\2.5.0\spring-boot-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-logging\2.5.0\spring-boot-starter-logging-2.5.0.jar;E:\develop\maven_repository\maven_repository\ch\qos\logback\logback-classic\1.2.3\logback-classic-1.2.3.jar;E:\develop\maven_repository\maven_repository\ch\qos\logback\logback-core\1.2.3\logback-core-1.2.3.jar;E:\develop\maven_repository\maven_repository\org\apache\logging\log4j\log4j-to-slf4j\2.14.1\log4j-to-slf4j-2.14.1.jar;E:\develop\maven_repository\maven_repository\org\apache\logging\log4j\log4j-api\2.14.1\log4j-api-2.14.1.jar;E:\develop\maven_repository\maven_repository\org\slf4j\jul-to-slf4j\1.7.30\jul-to-slf4j-1.7.30.jar;E:\develop\maven_repository\maven_repository\jakarta\annotation\jakarta.annotation-api\1.3.5\jakarta.annotation-api-1.3.5.jar;E:\develop\maven_repository\maven_repository\org\yaml\snakeyaml\1.28\snakeyaml-1.28.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-json\2.5.0\spring-boot-starter-json-2.5.0.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\core\jackson-databind\2.12.3\jackson-databind-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\core\jackson-annotations\2.12.3\jackson-annotations-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\core\jackson-core\2.12.3\jackson-core-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\datatype\jackson-datatype-jdk8\2.12.3\jackson-datatype-jdk8-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\datatype\jackson-datatype-jsr310\2.12.3\jackson-datatype-jsr310-2.12.3.jar;E:\develop\maven_repository\maven_repository\com\fasterxml\jackson\module\jackson-module-parameter-names\2.12.3\jackson-module-parameter-names-2.12.3.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-tomcat\2.5.0\spring-boot-starter-tomcat-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\apache\tomcat\embed\tomcat-embed-core\9.0.46\tomcat-embed-core-9.0.46.jar;E:\develop\maven_repository\maven_repository\org\apache\tomcat\embed\tomcat-embed-el\9.0.46\tomcat-embed-el-9.0.46.jar;E:\develop\maven_repository\maven_repository\org\apache\tomcat\embed\tomcat-embed-websocket\9.0.46\tomcat-embed-websocket-9.0.46.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-web\5.3.7\spring-web-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-beans\5.3.7\spring-beans-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-webmvc\5.3.7\spring-webmvc-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-aop\5.3.7\spring-aop-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-context\5.3.7\spring-context-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-expression\5.3.7\spring-expression-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\quartz-scheduler\quartz\2.3.2\quartz-2.3.2.jar;E:\develop\maven_repository\maven_repository\com\mchange\mchange-commons-java\0.2.15\mchange-commons-java-0.2.15.jar;E:\develop\maven_repository\maven_repository\org\slf4j\slf4j-api\1.7.30\slf4j-api-1.7.30.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-core\5.3.7\spring-core-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-jcl\5.3.7\spring-jcl-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\projectlombok\lombok\1.18.20\lombok-1.18.20.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-boot-starter\3.3.1\mybatis-plus-boot-starter-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus\3.3.1\mybatis-plus-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-extension\3.3.1\mybatis-plus-extension-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-core\3.3.1\mybatis-plus-core-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\baomidou\mybatis-plus-annotation\3.3.1\mybatis-plus-annotation-3.3.1.jar;E:\develop\maven_repository\maven_repository\com\github\jsqlparser\jsqlparser\3.1\jsqlparser-3.1.jar;E:\develop\maven_repository\maven_repository\org\mybatis\mybatis\3.5.3\mybatis-3.5.3.jar;E:\develop\maven_repository\maven_repository\org\mybatis\mybatis-spring\2.0.3\mybatis-spring-2.0.3.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-autoconfigure\2.5.0\spring-boot-autoconfigure-2.5.0.jar;E:\develop\maven_repository\maven_repository\org\springframework\boot\spring-boot-starter-jdbc\2.5.0\spring-boot-starter-jdbc-2.5.0.jar;E:\develop\maven_repository\maven_repository\com\zaxxer\HikariCP\4.0.3\HikariCP-4.0.3.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-jdbc\5.3.7\spring-jdbc-5.3.7.jar;E:\develop\maven_repository\maven_repository\org\springframework\spring-tx\5.3.7\spring-tx-5.3.7.jar" com.example.QuartzExample
22:29:49.528 [main] INFO org.quartz.impl.StdSchedulerFactory - Using default implementation for ThreadExecutor
22:29:49.539 [main] INFO org.quartz.simpl.SimpleThreadPool - Job execution threads will use class loader of thread: main
22:29:49.559 [main] INFO org.quartz.core.SchedulerSignalerImpl - Initialized Scheduler Signaller of type: class org.quartz.core.SchedulerSignalerImpl
22:29:49.559 [main] INFO org.quartz.core.QuartzScheduler - Quartz Scheduler v.2.3.2 created.
22:29:49.559 [main] INFO org.quartz.simpl.RAMJobStore - RAMJobStore initialized.
22:29:49.559 [main] INFO org.quartz.core.QuartzScheduler - Scheduler meta-data: Quartz Scheduler (v2.3.2) 'DefaultQuartzScheduler' with instanceId 'NON_CLUSTERED'Scheduler class: 'org.quartz.core.QuartzScheduler' - running locally.NOT STARTED.Currently in standby mode.Number of jobs executed: 0Using thread pool 'org.quartz.simpl.SimpleThreadPool' - with 10 threads.Using job-store 'org.quartz.simpl.RAMJobStore' - which does not support persistence. and is not clustered.22:29:49.559 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler 'DefaultQuartzScheduler' initialized from default resource file in Quartz package: 'quartz.properties'
22:29:49.559 [main] INFO org.quartz.impl.StdSchedulerFactory - Quartz scheduler version: 2.3.2
22:29:49.561 [main] INFO org.quartz.core.QuartzScheduler - Scheduler DefaultQuartzScheduler_$_NON_CLUSTERED started.
22:29:49.561 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 0 triggers
22:29:49.570 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:49.571 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:29:49.571 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:49.571 [DefaultQuartzScheduler_Worker-1] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:29:54.570 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:29:54.570 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:54.570 [DefaultQuartzScheduler_Worker-2] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:29:59.579 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:29:59.579 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:29:59.579 [DefaultQuartzScheduler_Worker-3] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:30:04.563 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:30:04.563 [DefaultQuartzScheduler_Worker-4] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:30:04.563 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:30:09.565 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:30:09.566 [DefaultQuartzScheduler_Worker-5] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
22:30:09.566 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:30:14.572 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.simpl.PropertySettingJobFactory - Producing instance of Job 'group1.myJob', class=com.example.job.MyJob
22:30:14.573 [DefaultQuartzScheduler_QuartzSchedulerThread] DEBUG org.quartz.core.QuartzSchedulerThread - batch acquisition of 1 triggers
22:30:14.573 [DefaultQuartzScheduler_Worker-6] DEBUG org.quartz.core.JobRunShell - Calling execute on job group1.myJob
JobDetail Message: Hello from JobDetail!
JobDetail Count: 0
Trigger Message: Hello from Trigger!
Trigger Count: 100
Merged Message: Hello from Trigger!
Merged Count: 100
……
可以看到一些其他信息:
quartz默认使用的是SimpleThreadPool,并且线程为10个。RAMJobStore 是 Quartz 默认的 Job 存储实现,它将所有的调度数据(如 JobDetail、Trigger、调度状态等)存储在内存中。RAMJobStore 不会将调度数据持久化到磁盘或数据库中。所有的数据都存储在内存中,一旦应用程序停止或崩溃,数据就会丢失。存在内存的好处是快。存在内存容易丢失,断电或者重启程序就丢失job、trigger等信息了,并且,内存存储的方式无法做集群部署的定时任务,只能做简单的单机应用的定时任务。如果需要quartz支持持久化或者集群,那么需要将 JobStore 切换为 JDBCJobStore。
从输出结果可以看到,Merged Count 的值始终是 100,没有递增。这是因为:默认情况下,Quartz 的 Job 是无状态的,每次执行时都会创建一个新的 Job 实例。对 JobDataMap 的修改不会影响下一次任务的执行。
监听器
监听器可以用于监控任务的执行、触发器的触发、调度器的启动和关闭等事件。Quartz 提供了三种监听器JobListener(任务监听器)、TriggerListener(触发器监听器)、 SchedulerListener(调度器监听器)
JobListener
用于监听与 Job 相关的事件,例如:
- 任务执行前触发(jobToBeExecuted)。
- 任务执行完成时触发(jobWasExecuted)。
- 任务执行失效时触发(jobExecutionVetoed)。
使用例子:
package com.example.listeners;import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.JobListener;public class MyJobListener implements JobListener {@Overridepublic String getName() {return "MyJobListener"; // 监听器的名称}@Overridepublic void jobToBeExecuted(JobExecutionContext context) {System.out.println("Job is about to be executed: " + context.getJobDetail().getKey());}@Overridepublic void jobExecutionVetoed(JobExecutionContext context) {System.out.println("Job execution was vetoed: " + context.getJobDetail().getKey());}@Overridepublic void jobWasExecuted(JobExecutionContext context, JobExecutionException jobException) {System.out.println("Job was executed: " + context.getJobDetail().getKey());if (jobException != null) {System.out.println("Job execution failed with exception: " + jobException.getMessage());}}
}
package com.example;import com.example.job.SimpleJob;
import com.example.listeners.MyJobListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;public class CronTriggerExample {public static void main(String[] args) {try {// 1. 获取任务调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 2. 创建任务实例JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("cronJob", "group1").build();// 3. 创建 CronTriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("cronTrigger", "group1").withSchedule(CronScheduleBuilder.cronSchedule("0 11 23 * * ?")).build();// 4. 把任务和触发器告诉任务调度器scheduler.scheduleJob(job, trigger);// 5. 注册任务监听器// scheduler.getListenerManager().addJobListener(new MyJobListener());// (监听当前调度器上的所有任务)// scheduler.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));// (只监听group1组的Job任务)scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("cronJob", "group1")));// (只监听group1组中叫cronJob的Job任务)// 6. 使用任务调度器启动任务scheduler.start();// 让任务运行一段时间Thread.sleep(60000); // 60秒// 7. 使用任务调度器关闭任务scheduler.shutdown();} catch (SchedulerException | InterruptedException e) {e.printStackTrace();}}
}
注意:一个任务调度器Scheduler是可以绑定多个任务的,所以,当我们给Scheduler中注册监听器的时候,要说明是监听这个调度器所有的任务,还是只监听某个组的任务,还是说只监听某个key的任务。
TriggerListener
用于监听与 Trigger 相关的事件,例如:
- 触发器触发时触发(triggerFired)。
- 触发器触发完成时触发(triggerComplete)。
- 触发器错过触发时触发(triggerMisfired)。
- 触发器执行失效时触发(vetoJobExecution)。
例子:
package com.example.listeners;import org.quartz.JobExecutionContext;
import org.quartz.Trigger;
import org.quartz.TriggerListener;public class MyTriggerListener implements TriggerListener {@Overridepublic String getName() {return "MyTriggerListener"; // 监听器的名称}@Overridepublic void triggerFired(Trigger trigger, JobExecutionContext context) {System.out.println("Trigger fired: " + trigger.getKey());}@Overridepublic boolean vetoJobExecution(Trigger trigger, JobExecutionContext context) {System.out.println("Checking if job execution should be vetoed for trigger: " + trigger.getKey());return false; // 返回 true 表示否决任务执行}@Overridepublic void triggerMisfired(Trigger trigger) {System.out.println("Trigger misfired: " + trigger.getKey());}@Overridepublic void triggerComplete(Trigger trigger, JobExecutionContext jobExecutionContext, Trigger.CompletedExecutionInstruction completedExecutionInstruction) {System.out.println("Trigger completed: " + trigger.getKey());}}
package com.example;import com.example.job.SimpleJob;
import com.example.listeners.MyJobListener;
import com.example.listeners.MyTriggerListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;public class CronTriggerExample {public static void main(String[] args) {try {// 1. 获取任务调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 2. 创建任务实例JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("cronJob", "group1").build();// 3. 创建 CronTriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("cronTrigger", "group1").withSchedule(CronScheduleBuilder.cronSchedule("0 11 23 * * ?")).build();// 4. 把任务和触发器告诉任务调度器scheduler.scheduleJob(job, trigger);// 5. 注册任务监听器// scheduler.getListenerManager().addJobListener(new MyJobListener());// (监听当前调度器上的所有任务)// scheduler.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));// (只监听group1组的Job任务)// scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("cronJob", "group1")));// (只监听group1组中叫cronJob的Job任务)// 5. 注册触发器监听器scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());// 注册触发器监听器// 6. 使用任务调度器启动任务scheduler.start();// 让任务运行一段时间Thread.sleep(60000); // 60秒// 7. 使用任务调度器关闭任务scheduler.shutdown();} catch (SchedulerException | InterruptedException e) {e.printStackTrace();}}
}
SchedulerListener
用于监听与 Scheduler 相关的事件,例如:
- 调度器启动时(schedulerStarted)。
- 调度器关闭时(schedulerShutdown)。
- 任务或触发器被添加、删除、暂停、恢复时。
例子:
package com.example.listeners;import org.quartz.*;public class MySchedulerListener implements SchedulerListener {@Overridepublic void jobScheduled(Trigger trigger) {System.out.println("Job scheduled: " + trigger.getJobKey());}@Overridepublic void jobUnscheduled(TriggerKey triggerKey) {System.out.println("Job unscheduled: " + triggerKey);}@Overridepublic void triggerFinalized(Trigger trigger) {System.out.println("Trigger finalized: " + trigger.getKey());}@Overridepublic void triggerPaused(TriggerKey triggerKey) {System.out.println("Trigger paused: " + triggerKey);}@Overridepublic void triggersPaused(String triggerGroup) {System.out.println("Triggers paused in group: " + triggerGroup);}@Overridepublic void triggerResumed(TriggerKey triggerKey) {System.out.println("Trigger resumed: " + triggerKey);}@Overridepublic void triggersResumed(String triggerGroup) {System.out.println("Triggers resumed in group: " + triggerGroup);}@Overridepublic void jobAdded(JobDetail jobDetail) {System.out.println("Job added: " + jobDetail.getKey());}@Overridepublic void jobDeleted(JobKey jobKey) {System.out.println("Job deleted: " + jobKey);}@Overridepublic void jobPaused(JobKey jobKey) {System.out.println("Job paused: " + jobKey);}@Overridepublic void jobsPaused(String jobGroup) {System.out.println("Jobs paused in group: " + jobGroup);}@Overridepublic void jobResumed(JobKey jobKey) {System.out.println("Job resumed: " + jobKey);}@Overridepublic void jobsResumed(String jobGroup) {System.out.println("Jobs resumed in group: " + jobGroup);}@Overridepublic void schedulerError(String msg, SchedulerException cause) {System.out.println("Scheduler error: " + msg);cause.printStackTrace();}@Overridepublic void schedulerInStandbyMode() {System.out.println("Scheduler in standby mode");}@Overridepublic void schedulerStarted() {System.out.println("Scheduler started");}@Overridepublic void schedulerStarting() {System.out.println("Scheduler starting");}@Overridepublic void schedulerShutdown() {System.out.println("Scheduler shutdown");}@Overridepublic void schedulerShuttingdown() {System.out.println("Scheduler shutting down");}@Overridepublic void schedulingDataCleared() {System.out.println("Scheduling data cleared");}
}
package com.example;import com.example.job.SimpleJob;
import com.example.listeners.MyJobListener;
import com.example.listeners.MySchedulerListener;
import com.example.listeners.MyTriggerListener;
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.quartz.impl.matchers.KeyMatcher;public class CronTriggerExample {public static void main(String[] args) {try {// 1. 获取任务调度器Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();// 2. 创建任务实例JobDetail job = JobBuilder.newJob(SimpleJob.class).withIdentity("cronJob", "group1").build();// 3. 创建 CronTriggerTrigger trigger = TriggerBuilder.newTrigger().withIdentity("cronTrigger", "group1").withSchedule(CronScheduleBuilder.cronSchedule("0 20 23 * * ?")).build();// 4. 把任务和触发器告诉任务调度器scheduler.scheduleJob(job, trigger);// 5. 注册任务监听器// scheduler.getListenerManager().addJobListener(new MyJobListener());// (监听当前调度器上的所有任务)// scheduler.getListenerManager().addJobListener(new MyJobListener(), GroupMatcher.jobGroupEquals("group1"));// (只监听group1组的Job任务)// scheduler.getListenerManager().addJobListener(new MyJobListener(), KeyMatcher.keyEquals(new JobKey("cronJob", "group1")));// (只监听group1组中叫cronJob的Job任务)// 5. 注册触发器监听器// scheduler.getListenerManager().addTriggerListener(new MyTriggerListener());// 注册触发器监听器// 5. 注册调度器监听器scheduler.getListenerManager().addSchedulerListener(new MySchedulerListener());// 注册调度器监听器// 6. 使用任务调度器启动任务scheduler.start();// 让任务运行一段时间Thread.sleep(60000); // 60秒// 7. 使用任务调度器关闭任务scheduler.shutdown();} catch (SchedulerException | InterruptedException e) {e.printStackTrace();}}
}
其实也是和前面两个类似的。
Quartz存储及持久化
JobStore
JobStore负责存储调度器的工作数据(Job、Trigger、JobDataMap),通俗地讲,就是JobStore中存什么,调度器就会执行什么。
其实,Quartz是先把数据存到JobStore中,然后再使用Scheduler去JobStore中拿信息进行调度的。即,Scheduler 接收到 JobDetail 和 Trigger 后,会将它们的相关数据存储到 JobStore 中。scheduler.scheduleJob(job, trigger);这个语句会把相关数据存储到 JobStore 中。Scheduler 启动后(scheduler.start()),它会从 JobStore 中读取任务和触发器的数据,按预定计划进行执行。
Quartz默认是存储到内存的,所以可能会出现下面这个问题:比如我们要执行100次任务,当执行了40次时系统崩溃了,系统重启时任务的执行计数器默认会从0开始。虽然这种策略能够满足多数业务场景需求。但是在某个特定的场景中我们需要继续之前的任务执行,我们可以通过对Jobstore进行持久化来实现。
Quartz提供了两种作业存储方式:
- RAMJobStore:基于内存,重启服务器会丢失数据,但是速度很快。不适合集群部署的情况。
- JDBCJobStore:基于数据库,重启服务器不会丢失数据,速度会比内存慢一点。适合集群部署的情况。
注意:如果集群部署应用,那么你就必须使用JDBCJobStore了,因为,如果多个地方部署Quartz的应用,那么就会有多个调度器了,并且如果都使用内存保存JobStore信息,那么,一个任务可能会被多次执行。但是,如果你多个应用的多个调度器使用一个DB,保存JobStore信息,那么执行就不会重复了,因为执行的信息保存到一个地方,A调度器执行了,告诉数据库,B调度器去执行前,先看数据库,就知道这个任务被A执行了,所以B不会再执行了,这样就不会进行多次执行同一个任务了。
要Quartz使用JDBCJobStore,那么需要自己进行额外的配置的,因为默认是使用RAMJobStore的。集群部署时,需要在多个应用的调度器中配置JDBCJobStore时,使用同一个数据库就行了。
Job主要有volatility、durability两个属性。其中,volatility表示任务是否被持久化,durability表示再没有trigger关联的时候,任务是否被保留。
相关文章:
Quartz知识点总结
简单说明 简单的定时任务使用Timer或者ScheduledExecutorService quartz支持复杂的定时执行功能。支持ram存储(内存存储)和持久化存储。quartz有分布式和集群能力 简单使用 获取任务调度器Schedule。任务调度器可以管理任务。创建任务实例。使用JobB…...
P2786 英语1(eng1)- 英语作文
P2786 英语1(eng1)- 英语作文 题目背景 蒟蒻 HansBug 在英语考场上,挠了无数次的头,可脑子里还是一片空白。 题目描述 眼下出现在 HansBug 蒟蒻面前的是一篇英语作文,然而智商捉急的 HansBug 已经草草写完了&#…...
Clion远程开发配置
代码开发环境:windows下,基于Clion 2024.3开发,标准为C20 代码运行环境:远程服务器,ubuntu,cmake版本3.12,gcc11.4,g11.4,gdb12.1 实现功能:在本地windows开…...
Javascript基础
目录 1. 变量声明2. 基本数据类型3.复杂数据类型4.字符串方法5.对象方法6.时间方法7.条件(if)8.循环(for/while)9.遍历(for in/of)10.多选(Switch)END 1. 变量声明 const࿱…...
蓝桥杯2023年第十四届省赛真题-阶乘的和
蓝桥杯2023年第十四届省赛真题-阶乘的和 时间限制: 2s 内存限制: 320MB 提交: 3519 解决: 697 题目描述 给定 n 个数 Ai,问能满足 m! 为∑ni1(Ai!) 的因数的最大的 m 是多少。其中 m! 表示 m 的阶乘,即 1 2 3 m。 输入格式 输入的第一行包含一个整…...
供应链优化售前方案建议书V23(58页PPT)(文末有下载方式)
随着家电行业的快速发展,供应链管理已成为企业竞争的关键要素。杭州松下电器在面对日益复杂的市场环境和激烈的市场竞争时,急需对其供应链进行优化。本文将对杭州松下电器的供应链优化方案进行详细解读,探讨其优化策略及其潜在价值。 供应链…...
校园论坛系统Selenium自动化测试
本文为自动化测试 本项目自动化测试代码链接(仅供参考): 自动化测试代码 功能测试文章链接: 校园论坛系统自动化测试报告-CSDN博客 🌈自动化测试 思维导图 根据思维导图, 我们选取几个主要的功能进行自动化测试 编写代码 思路: 根据脑图进行测试用例的编写&am…...
Linux 一步部署DHCP服务
#!/bin/bash #脚本作者和日期 #author: PEI #date: 20250319 #检查root权限 if [ "$USER" ! "root" ]; then echo "错误:非root用户,权限不足!" exit 0 fi #防火墙与高级权限 systemctl stop firewa…...
Cool Request:可以统计任意方法耗时
什么是Cool Request Cool Request是一个IDEA中的接口调试插件,除了可以发起基本的HTTP请求之外,还提供了强大的反射调用能力,可以绕过拦截器,这点广受网友的好评,当然伴随着还有Spring中对Scheduled注解的调用&#x…...
基于Spring Boot的图书管理系统的设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
Python实战(2)-数据库支持
使用简单的纯文本文件可实现的功能有限。诚然,使用它们可做很多事情,但有时可能还需要额外的功能。你可能希望能够自动完成序列化,此时可求助于shelve和pickle(类似于shelve)。不过你可能需要比这更强大的功能。例如…...
【工具】isolateR桑格测序数据的自动化处理、分类分析以及微生物菌株库的生成R包
文章目录 介绍代码案例Step 1: isoQC - Automated quality trimming of sequencesStep 2: isoTAX - Assign taxonomyStep 3: isoLIB - Generate strain library 参考 介绍 对分类标记基因(如16S/18S/ITS/rpoB/cpn60)进行桑格测序是鉴定包括细菌、古菌和…...
比特币牛市还在不在
在加密货币的风云世界里,比特币的一举一动始终牵动着投资者们的神经。近期比特币的涨幅动作,再次引发了市场对于牛市是否仍在延续的激烈讨论。 在深入探索比特币市场的过程中,获取全面且及时的资讯至关重要。您可以通过访问Techub News&#…...
鸿蒙下载文件保存到手机本地公共文件夹下、将本地的沙箱目录文件,保存到公共目录,鸿蒙picker save保存文件为空(0字节)的问题
1、首先将下载好的文件,保存到本地目录,这个目录是用户看不到的; 2、然后通过picker的save保存文件,这个picker,它只是获取公共目录uri用的 3、当picker有回调时,将公共目录的uri获取之后,把下…...
红日靶场(二)——个人笔记
靶场搭建 新增VMnet2网卡 **web:**需要配置两张网卡,分别是外网出访NAT模式和内网域环境仅主机模式下的VMnet2网卡。 **PC:**跟web一样,也是需要配置两张网卡,分别是外网出访NAT模式和内网域环境仅主机模式下的VMn…...
口袋书签功能上新,免费使用
丰富主页面的菜单,操作更加便捷。 快来构建你的门户站点吧。 戳: 口袋书签...
Model Context Protocol - Prompts
1. 概述 Model Context Protocol (MCP) 提供了一种标准化的方式,使服务器能够向客户端暴露提示模板(prompts)。Prompts 是服务器提供的结构化消息和指令,用于与语言模型进行交互。客户端可以发现可用的提示、获取其内容ÿ…...
零知识证明:区块链隐私保护的变革力量
🧑 博主简介:CSDN博客专家,历代文学网(PC端可以访问:https://literature.sinhy.com/#/literature?__c1000,移动端可微信小程序搜索“历代文学”)总架构师,15年工作经验,…...
基于STC89C52的CD4511译码显示数字设计
摘要 本文深入探讨基于STC89C52单片机的数字显示系统设计,剖析CD4511译码驱动芯片工作原理,结合Proteus仿真验证功能。通过硬件电路、软件编程及原理分析,完整呈现单片机控制数码管显示的实现过程,为相关开发提供理论与实践参考。 一、引言 在单片机应用中,数码管显示是…...
MPC算法路径跟踪_Matlab实现
在机器人控制领域,模型预测控制(MPC)因其能够处理动态约束和多目标优化的特性,成为路径跟踪的热门方案。近期,我在 GitHub 上发现了 Mr.Winter 的MPC路径规划项目,其代码实现简洁且功能完整。本文将结合理论…...
QT Quick(C++)跨平台应用程序项目实战教程 2 — 环境搭建和项目创建
目录 引言 1. 安装Qt开发环境 1.1 下载Qt安装包 1.2 安装Qt 1.3 安装Visual Studio 2022 1.4 在Visual Studio 2022中安装Qt插件 1.5 在Visual Studio 2022中安装大模型编程助手 2. 创建Qt Quick项目 2.1 创建新项目 2.2 项目结构 2.3 运行项目 3. 理解项目代码 3…...
洛科威多功能岩棉板为环保助力,推动企业绿色可持续发展
在当今全球环保意识日益增强的背景下,企业工程项目在追求高效益的同时,也更加注重绿色可持续发展。作为建筑材料领域的佼佼者,洛科威公司推出的多功能岩棉板凭借其卓越的绿色环保特性,正逐渐成为企业工程项目领域的首选材料。 洛科…...
7.3《重力》
教会什么:重力及其三要素、重力加速度g、 培养什么:从力的三要素出发去研究一个力,用所学探究未知 课标: (二)运动和相互作用 2.2 机械运动和力 2.2.3 通过常见事例或实验,了解重力,认识力的作用效果。 (四)实验探究 4.1.6 用弹测力计测量力。 例6 测量一本物理教科书…...
虚幻基础:ue自定义类
文章目录 Gameplay Tag:ue标签类创建:其他-数据表格-gameplaytag安装:项目设置:gamePlayTag:gamePlay标签列表使用:变量类型:gamePlayTag primary data asset:ue数据类:通…...
88页手册上线 | 企业级本地私有化DeepSeek实战指南
DeepSeek为普通企业在低成本、高性能、安全可控的前提下私有化部署AI大模型提供了可行路径。 云轴科技ZStack全新推出《企业级本地私有化DeepSeek实战手册》(点击免费下载),直击企业痛点,从7B轻量化模型到671B超大规模部署&#…...
Godot读取json配置文件
概述 在Godot 4.3中读取JSON配置文件,可以通过以下步骤实现: 步骤说明 读取文件内容:使用FileAccess类打开并读取JSON文件。 解析JSON数据:使用JSON类解析读取到的文本内容。 错误处理:处理文件不存在或JSON格式错…...
时序分析笔记
提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 一、周期约束 二、建立时间和保持时间 三、时序路径 四、时序模型 前言 约束文件笔记,傅里叶的猫的视频。 一、周期约束 时序约束就是告诉软件输…...
【笔记】深度学习模型训练的 GPU 内存优化之旅:重计算篇
开设此专题,目的一是梳理文献,目的二是分享知识。因为笔者读研期间的研究方向是单卡上的显存优化,所以最初思考的专题名称是“显存突围:深度学习模型训练的 GPU 内存优化之旅”,英文缩写是 “MLSys_GPU_Memory_Opt”。…...
Deepseek使用技巧大全
还有好多人不会用,一个链接让你们全部学完 https://m0739kfdebc.feishu.cn/docx/LIBddUcupoIBwVxp0yGcsT77nFd?fromfrom_copylink...
redis搭建一主一从+keepalived(虚拟IP)实现高可用
redis搭建一主一从keepalived(虚拟IP)实现高可用 前提 有两台机器:如 10.50.3.141 10.50.3.142,虚拟ip如:10.50.3.170 安装redis(两台机器执行): # 启用Remi仓库(CentOS 7) sudo yum install…...
6、说一下索引失效的场景?【中高频】
索引失效意味着 查询操作 不能利用索引进行数据检索,而是使用 全表扫描(也就是 数据库需要从磁盘上读取表的所有数据行),从而导致性能下降,下面一些场景会发生索引失效 对索引使用左或者左右模糊匹配(where…...
前端调试实战指南:从入门到高阶的完整解决方案
引言:调试的本质与价值 调试是程序员将理想代码映射到现实运行环境的关键过程。据统计,开发者平均将30%的工作时间用于调试。本指南将系统梳理现代前端调试技术体系,帮助开发者构建高效的调试工作流。 一、基础调试工具箱 1.1 浏览器开发者工具核心功能 元素调试(Elemen…...
电商多包裹与子母单发货区别
在电商发货中,多包裹发货和子母单是两种常见的发货方式,具体含义如下: 1. 多包裹发货 定义: 指一个订单中的商品因库存、尺寸或重量等原因,无法装入一个包裹,需分成多个包裹发出。 原因: 商品…...
程序化广告行业(28/89):基于用户旅程的广告策略解析
程序化广告行业(28/89):基于用户旅程的广告策略解析 大家好!一直以来,我都希望能和大家在技术学习的道路上携手前行、共同进步。在之前的文章里,我们探讨了程序化广告行业的诸多关键环节,这次让…...
Hugging Face模型国内镜像HF Mirror下载
直接下载 Hugging Face 开启梯子,一看好几个g... 我们寻找国内镜像。 访问HF-Mirror 继续上面搜索。 继续点击跟踪路径。 拼出路径。 https://hf-mirror.com/Comfy-Org/Wan_2.1_ComfyUI_repackaged/resolve/main/split_files/vae/wan_2.1_vae.safetensors 如果网…...
2025-03-19 学习记录--C/C++-C语言-单链表的结构体定义 + LNode * 和 LinkList 的区别
C语言-单链表的结构体定义 ⭐️ 一、单链表的结构体定义 🍭 typedef struct LNode { // 定义结构体 LNode,表示链表中的一个结点int data; // 数据域,存储结点的值struct LNode *next; // 指针域,指向下一个结点 } LN…...
【操作系统安全】任务7:服务与进程
目录 一、引言 二、服务与进程介绍 2.1 服务的概念 2.2 进程的概念 2.3 服务与进程的关系 2.4 服务与进程在网络安全中的重要性 三、LAMP 网站环境部署 3.1 LAMP 简介 3.2 LAMP 环境部署步骤 3.2.1 安装 Linux 操作系统 3.2.2 安装 Apache HTTP 服务器 3.2.3 安装 …...
AI里的RAG到底是什么?
AI大模型如deepseek本地部署的成本相对较低,如果要训练,微调大模型,则需要非常多的显卡,与很多时间,那一般企业无法投入那么多钱去买显卡,怎么办? 通过RAG与本地部署来提升大模型的专业知识 R…...
数据库从安装到劝退
友好的安装是数据库使用的第一步 MySQL被称为5分钟数据库。是形容安装简单。事实也是如此。RPM一下可以就把几个包安装完毕了。一个单机情况下,5分钟是足够的。 其他数据库PostgreSQL也差不多是这样。 而Redis这种就更快了。所以这些才能流行。 曾经数据库中安装相…...
《基于Spring Boot+Vue的智慧养老系统的设计与实现》开题报告
个人主页:@大数据蟒行探索者 一、研究背景及国内外研究现状 1.研究背景 根据1982年老龄问题世界大会联合国制定的标准,如果一个国家中超过65岁的老人占全国总人口的7%以上,或者超过60岁的老人占全国总人口的10%以上,那么这个国家将被定义为“老龄化社会”[1]。 随着国…...
PHP转GO Go语言环境搭建(Day1) 常见问题及解决方案指南
Go语言环境搭建(Day1)整理的 常见问题及解决方案指南: Go环境搭建问题排查手册 一、安装阶段问题 问题现象原因分析解决方案安装包下载失败网络问题或官网访问慢使用国内镜像下载:- Go中文网提示"Access Denied"Windows系统权限不足1. 右键安装包选择"以管理…...
VLLM专题(三十九)—自动前缀缓存(二)
前缀缓存(Prefix Caching)是一种在LLM推理中广泛使用的优化技术,旨在避免冗余的提示词(prompt)计算。其核心思想很简单——我们缓存已处理请求的键值缓存(kv-cache)块,并在新请求的前缀与之前请求相同时重用这些块。由于前缀缓存几乎是一种“免费的午餐”,并且不会改变…...
C语言每日一练——day_12(最后一天)
引言 针对初学者,每日练习几个题,快速上手C语言。第十二天。(最后一天,完结散花啦) 采用在线OJ的形式 什么是在线OJ? 在线判题系统(英语:Online Judge,缩写OJ࿰…...
HAL库编程知识点---Can.c和Driver_can.c分层开发
在一个工程中,通常会把对CAN外设的操作分成底层和上层两个部分,从而提高代码的模块化和可维护性。一般来说: can.c 通常由硬件抽象层(HAL)或者自动生成工具(如 CubeMX)提供或生成。主要负责CAN硬…...
L2TP实验 作业
拓扑图 实验需求 让FW1(PPPoE Client)模拟拨号用户,向内部服务器发送建立拨号连接的请求,并保证连通 实验步骤 安全区域 firewall zone trust add int g1/0/0 策略 security-policy default action permit NAS int g1/…...
算法模型从入门到起飞系列——递归(探索自我重复的奇妙之旅)
文章目录 前言一、递归本质1.1 递归的要素1.2 递归特点 二、递归&迭代2.1 递归&迭代比较2.2 递归&迭代如何实现相同功能2.2.1 递归实现2.2.2 迭代实现2.2.3 性能对比 三、优雅的递归理解3.1 阶乘计算分解3.2 [DFS](https://blog.csdn.net/qq_38315952/article/deta…...
Netty源码—1.服务端启动流程二
大纲 1.服务端启动整体流程及关键方法 2.服务端启动的核心步骤 3.创建服务端Channel的源码 4.初始化服务端Channel的源码 5.注册服务端Channel的源码 6.绑定服务端端口的源码 7.服务端启动流程源码总结 5.注册服务端Channel的源码 (1)注册服务端Channel的入口 (2)注册…...
Python OCR文本识别详细步骤及代码示例
光学字符识别(OCR)是将图像中的文字转换为可编辑文本的技术。在Python中,我们可以利用多种库实现OCR功能。本文将详细介绍使用Tesseract和EasyOCR进行文本识别的步骤,并提供完整的代码示例。 一、OCR简介 OCR(Optical…...
springmvc 框架学习
什么是 SpringMVC 框架 Spring MVC 是 Spring 框架的核心模块之一,基于 Java Servlet API 构建的 Web 层解决方案。它实现了 MVC 设计模式(Model-View-Controller),专为开发灵活、松耦合的 Web 应用程序而设计。 在控制层框架历…...
学习threejs,构建THREE.ParametricGeometry参数化函数生成几何体
👨⚕️ 主页: gis分享者 👨⚕️ 感谢各位大佬 点赞👍 收藏⭐ 留言📝 加关注✅! 👨⚕️ 收录于专栏:threejs gis工程师 文章目录 一、🍀前言1.1 ☘️THREE.ParametricGeometry1…...