1.小顶堆(适合任务少的,因为向下调整耗费性能)
堆:是一完全二叉树(除了最后一层节点其他层都达到最大节点数,且最后一层都靠左排列);堆中某个节点的值总不大于或不小于其父节点。
定时任务是基于小顶堆。每一个job都对应每一个节点,节点值里面存储job的到期时间(Delay),每次取堆顶的节点去执行。
插入元素:插入尾部,然后上浮(每次与n/(2^x)的位置济宁比较,时间复杂度O(log n)。
删除堆顶元素:将尾部元素放到堆顶,然后下浮。第一个任务(最近)执行后(堆顶元素)就进行这个操作。
2.时间轮算法(适合任务多)
*链表或者数组实现时间轮询:while-true-sleep。遍历数组,每个下标放置一个链表,链表节点放置任务,遍历到了就取出执行。
*round型时间轮询:任务上记录一个round,遍历到了就将round减一,为0时取出执行(需要遍历所有的任务,效率较低)。
*分层时间轮(cron表达式):使用多个时间维度的轮,天轮:记录几点执行,月轮:记录几号执行。月轮遍历到了,将任务取出放去天轮里面,即可实现几号几点执行。
1.timer的使用
2.timer的数据结构和原理分析
*TaskQueue:小顶堆,存放timeTask
*TimerThread:任务执行线程,死循环不断检查是否有任务需要开始执行,有就执行,还是在这个线程执行。
*单线程执行任务,会阻塞。Scheaule:任务执行超时,会导致后面的任务往后推移。预想在这个间隔执行的任务就没了;ScheduleAtFixedRate:任务超时可能导致下一个任务马上执行。
*运行时异常会导致timer线程终止
*任务调度是基于绝对时间的,对系统时间敏感。
3.timer中存在的问题
4.timer的应用场景分析
/**
* Timer:
* 放入queue(小堆)中去执行
* 底层task调用run方法,单线程执行,任务时长超过间隔时间会乱
* 单线程:任务阻塞,导致超时,解决:使用线程池
*/
public class TimerTest {
public static void main(String[] args) {
Timer timer = new Timer(); //任务启动
for (int i = 0; i < 2 ;i++){
TimerTask task = new FoolTimerTask("foo" + i);
//添加任务
/**
* schedule:真正的执行时间,取决于上一个任务的结束时间 (任务超过间隔时间就会丢失任务)
* scheduleAtFixedRate:严格按照预设时间 (任务超过间隔时间执行时间会乱)
*/
timer.schedule(task,new Date(),2000); //任务,启动时间,间隔时间
}
}
}
class FoolTimerTask extends TimerTask{
private String name;
public FoolTimerTask(String name) {
this.name = name;
}
@Override
public void run() {
long startTimer = System.currentTimeMillis();
try {
Thread.sleep(3000);
//使用线程池避免任务阻塞
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTimer = System.currentTimeMillis();
System.out.println(endTimer-startTimer);
}
}
1.使用简介
2.单线程版和多线程版
单线程见上面代码
3.数据结构、原理分析
*ScheduledThreadPoolExecutor:
使用多线程执行任务,不会相互阻塞
如果线程失活,会新建线程执行任务(线程抛异常,任务会被丢弃,需要捕获异常)
DelayWorkQueue:小顶堆、无界队列
1.在定时线程池中,最大线程数是没有意义的
2.执行时间距离当前时间越近的任务就在队列的最前面
3.用于添加ScheduleFutureTask(继承于FutureTask,实现RunnableScheduleFuture接口)
4.线程池中的线程从DelayQueue中获取ScheduleFutureTask执行
5.实现了Delay接口,可以通过getDelay方法来获取延迟时间
*SingleScheduledThreadPoolExecutor:
单线程的线程池:适用于单个后台的线程执行周期,同时又保证任务顺序执行。
ScheduleThreadPoolExecutor
//一定延迟之后只执行一次某个任务
public ScheduleFuture> schedule(Runnable command, long delay, TimeUnit unit);
public
ScheduleFuture schedule(Callable callable, long delay, TimeUnit unit);// 会有返回值 //一定延迟之后周期性的执行某个任务
//间隔是固定的,无论上一个任务是否执行完成
public ScheduleFuture> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit);
//时间是不固定的,其会在周期任务的上一个任务执行完成之后才开始计时,并在指定的时间间隔之后才开始执行任务
public ScheduleFuture> scheduleAtSicedRate(Runnable command, long initialDelay, long delay, TimeUnit unit);
4.应用场景
适用于多个后台线程执行周期性任务,同时为了满足资源管理的需求限制后台的线程数
5.Leader-Follower模式
避免没必要的唤醒和阻塞的操作,所有的工作线程中只有一个leader线程,其他线程都是follower线程,都处理休眠中,当leader线程拿到任务后执行任务前,自己会变成follower线程,同时会选出新的leader来,才去执行任务。如果此时有下一个任务,就是新的leader线程来执行,并以此往复这个过程,当之前执行任务的线程执行完毕再回来时,会判断如果此时已经没有任务了,又或者有任务但是其他的线程作为leader线程,那么自己就休眠了,如果此时有任务但是没有leader线程,那么自己就会重新成为leader线程来执行任务。
/**
* 基于线程池的Timer:
* 解决单线程阻塞
*/
public class TimerPool {
public static void main(String[] args) {
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
for (int i = 0; i < 2; i++) {
scheduledThreadPool.schedule(new Task("task-" + i),2, TimeUnit.SECONDS);
scheduledThreadPool.scheduleAtFixedRate(new Task("task-" + i),0,2, TimeUnit.SECONDS);
}
}
}
class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
public void run() {
long startTimer = System.currentTimeMillis();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTimer = System.currentTimeMillis();
System.out.println(endTimer - startTimer);
}
}
1.使用简介
2.各组件介绍
Job:
封装成JobDetail设置属性,
@DisallowConcurrentExecution:禁止并发地执行同一个job定义(jobDetail定义的)的多个实例
@PersistJobDataAfterExectuion:持久化jobDetail中的JobDataMap(对trigger中的datamap无效)
如果一个任务不是持久化的,泽当没有触发器关联它的时候,Quartz会从Scheduler中删除它
如果一个任务请求恢复,一般是该任务执行期间发生了系统崩溃或者其他关闭进程的操作,当服务再次启动的时候,会再次执行该任务,此时,JobExecutionContext.isRecovdering()会返回true
Trigger:触发器,指定执行时间,开始结束时间等
*优先级:同时触发的trigger之间会比较优先级,如果trigger是可恢复的,在恢复之后再调度,优先级不变
*misfire,错误触发:
1.判断misfire的条件:job到达触发时间时没有被执行,被执行的延迟时间超过了Quartz配置的misfireThreshold阈值
2.产生的原因:当job达到触发时间时,所有线程都被其他job占用,没有可用线程;在job需要触发的时间点,schedule停止了;job使用了@DisallowConcurrentExecution注解,job不能并发执行,当达到下一个job执行点的时候,上一个任务还没有执行完;job指定了过去的开始时间
3.策略:默认使用MISFIRE_INSTRUCTION_SMART_POLICY策略;
SimpleTrigger:now*相关的策略,会立即执行第一个misfire的任务,同时修改startTime和repeatCount,因此会重新计算finalFireTime,原计划执行时间会被打乱;next*相关策略,不会立即执行misfire的任务,也不会修改startTime和repeatCount,因此finalFireTimeu也没有改变,发生了misfire也还是按照原计划执行
CronTrigger:MISFIRE_INSTRUCTION_IGNORE_SMART_POLICY(Quartz不会判断发生了misfire,立即执行所有发生了misfire的任务,然后按原计划执行);MISFIRE_INSTRUCTION_FIRE_ONCE_NOW(立即执行第一个发生misfire的任务,忽略其他发生misfire的任务,然后按原计划执行);MISFIRE_INSTRUCTION_DO_NOTHING(所有发生misfire的任务都被忽略,只是按照原计划执行)
*clendar:用于排除时间段
*SimpleTrigger:具体时间点,指定间隔重复执行
*CronTrigger:cron表达式
Schedule:调度器,基于trigger的设定执行job
*SchedulerFactory:
创建Scheduler;
DirectScheduleFactory:在代码里定制Schedule参数;
StdScheduleFactory:读取classpath下的quartz.properties配置来实例化Schedule
*jobStore:
存储运行时inxi的,包括Trigger,Scheduler,jobDetail,业务锁等
RamJobStore(内存实现)
JobStoreTX(JDBC,事务由Quartz管理)
JobStoreCMT(JDBC,使用容器事务)
ClusteredJobStore(集群实现)
TerracottaJobStore(Terracotta中间件)
*ThreadPool:SimpleThreadPool;自定义线程池
JobDataMap:保存任务实例的状态信息
jobDetail:默认只在Job被添加到调度程序scheduler的时候,存储一次关于该任务的状态信息数据,可以使用注解@PersisJobDataAfterExecution注解标明在一个任务执行完毕之后就存储一次
trigger:任务被多个触发器引用的时候,根据不同的触发时机,可以提供不同的输入条件
3.组件关系架构分析
4.监听器及插件
5.java项目整合quartz
org.springframework.boot
spring-boot-starter-quartz
/**
* @DisallowConcurrentExecution:禁止并发地执行同一个job定义(jobDetail定义的)的多个实例
* @PersistJobDataAfterExectuion:持久化jobDetail中的JobDataMap(对trigger中的datamap无效)
* Scheduler每次执行,都会根据JobDetail创建一个新的job实例,这样就可以规避并发访问的问题
*/
@DisallowConcurrentExecution
public class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) throws JobExecutionException {
System.out.println("MyJob executr:" + System.currentTimeMillis());
//可以从缓存的Mao里面获取数据,同名会覆盖
JobDataMap jobDetailMap = context.getJobDetail().getJobDataMap();
JobDataMap triggerMap = context.getTrigger().getJobDataMap();
JobDataMap mergeMap = context.getMergedJobDataMap();
}
}
public class TestJob {
public static void main(String[] args) {
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("job1","group1") //设置job的名字和组
.usingJobData("job","jobDetail") //可以放入信息数据,在业务逻辑中获取
.requestRecovery(true) //job可恢复,在其执行的时候,scheduler发生硬关闭,则当scheduler重新启动的时候,该job会被重新执行,此时,该job的JobExecutionContext.isRecovering()返回true
.build();
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("trigger1", "trigger1")
.usingJobData("trigger","trigger")
.startNow()
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(1).repeatForever() //也可以用cron
).build();
try {
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
注意:重启需要清除数据库中quartz相关表中信息,不然misfire会导致执行出错
pom
org.springframework.boot
spring-boot-starter-quartz
配置文件:spring-quartz.properties
//spring-quartz.properties
#===========================================================
#配置JobStore
#===========================================================
#JobDataMaps是否都为String类型,默认false
org.quartz.jobStore.useProperties=false
#表的前缀,默认QRTZ
org.quartz.jobStore.tablePrefix = QRTZ
#是都加入集群
org.quartz.jobStore.isClustere = true
#调度实例失效的检查时间间隔ms
org.quartz.jobStore.clusterCheckinInterval = 5000
#当设置为true时,此属性告诉Quartz在非托管JDBC连接上调用setTransactionIsolation
org.quartz.jobStore.txIsolationLevelReadCommitted = true
#数据保存方式为数据库持久化
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX
#数据库代理类,一般org.quratz.impl.jdbcjobstore.StdJDBCDelegate可以满足大部分数据库
#===========================================================
#Scheduler 调度器属性配置
#===========================================================
#调度标识名 集群中每一个实例都必须使用相同的名称
org.quartz.scheduler.instanceName = ClusterQuartz
#ID设置为自动获取 每一个必须不同
org.quartz.scheduler.instanceId = AUTO
#===========================================================
#配置ThreadPool
#===========================================================
#线程池的实现类(一般使用SimpleThreadPool即可满足所有客需求)
org.qquartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
#指定线程数,一般设置为1-100之间的整数,根据系统资源配置
org.qquartz.threadPool.threadCount = 5
#设置线程的优先级
org.qquartz.threadPool.threadPriority = 5
调度器配置:
/**
* 配置器调度类
*/
@Configuration
public class SchedulerConfig {
@Autowired
private DataSource dataSource;
@Bean
public Scheduler scheduler() throws IOException {
return schedulerFactoryBean().getScheduler();
}
@Bean
public SchedulerFactoryBean schedulerFactoryBean() throws IOException {
SchedulerFactoryBean factory = new SchedulerFactoryBean();
factory.setSchedulerName("cluster_scheduler"); //设置调度器名字
factory.setDataSource(dataSource); //设置数据源
factory.setApplicationContextSchedulerContextKey("application"); //在IOCbean中的名字
factory.setQuartzProperties(quartzProperties()); //设置配置文件
factory.setTaskExecutor(schedulerThreadPool()); //设置线程池
factory.setStartupDelay(0); //设置执行delay
return factory;
}
/**
* 获取properties
*/
@Bean
public Properties quartzProperties() throws IOException {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("/spring-quartz.properties"));
propertiesFactoryBean.afterPropertiesSet();
return propertiesFactoryBean.getObject();
}
/**
* 获取线程池配置
*/
@Bean
public Executor schedulerThreadPool(){
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(Runtime.getRuntime().availableProcessors()); //取主机的核心线程数
executor.setMaxPoolSize(Runtime.getRuntime().availableProcessors());
executor.setQueueCapacity(Runtime.getRuntime().availableProcessors());
return executor;
}
}
springboot启动监听器:
/**
* springboot项目启动监听器
* 注意重启需要清除数据库的定时任务表信息,不然misfire会出错触发
*/
@Component
public class StartApplicationListener implements ApplicationListener {
@Autowired
private Scheduler scheduler;
@Override
public void onApplicationEvent(ContextClosedEvent event) {
//定义触发器,注意同意触发器只能存在一个实例
TriggerKey triggerKey = TriggerKey.triggerKey("trigger1", "group1");
try {
Trigger trigger = scheduler.getTrigger(triggerKey);
if (trigger == null) {
trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule("0/10 * * * * ?"))
.startNow() //设置立即启动,可不用
.build();
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
.withIdentity("job1", "group1")
.build();
//开始调度
scheduler.scheduleJob(jobDetail, trigger);
scheduler.start();
}
} catch (SchedulerException e) {
e.printStackTrace();
}
}
}
业务代码:
/**
* springboot 整合quartz
*/
@PersistJobDataAfterExecution
@DisallowConcurrentExecution
public class QuartzJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
try{
//打印任务相关信息
String schedulerInstanceId = context.getScheduler().getSchedulerInstanceId();
String name = context.getJobDetail().getKey().getName();
//调度任务相关业务代码
//TODO:...
}catch (Exception e){
e.printStackTrace();
}
}
}
find by:BV1WL4y177up