理论基础
小顶堆
参考 https://blog.csdn.net/zhizhengguan/article/details/106826270
时间轮算法
参考 https://blog.csdn.net/yu757371316/article/details/107320068
分层时间轮
JDK定时器 Timer使用及原理
1、timer的使用
3、timer中存在的问题
4、timer的应用场景分析
package com.wxl.example.timer;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class TimerTest {
public static void main(String[] args) {
Timer t = new Timer(); //任务启动
for (int i = 0; i<1; i++) {
TimerTask timerTask = new FoolTimerTask("task" + i);
// t.schedule(timerTask, new Date(),2000); //任务添加 任务执行超时 丢任务
t.scheduleAtFixedRate(timerTask, new Date(),2000); //任务添加 丢任务 执行时间混乱
//在 run中使用定时任务线程池规避
}
}
}
class FoolTimerTask extends TimerTask {
private String name;
public FoolTimerTask(String name) {
this.name = name;
}
@Override
public void run() {
try{
System.out.println("name=" + name + ",startTime=" + new Date());
Thread.sleep(3000);
System.out.println("name=" + name + ",endTime=" + new Date());
}catch(Exception e){
e.printStackTrace();
}
}
}
定时任务线程池解析
1、使用简介
线程池中每个独立任务串行执行,各任务执行并行
可以看到此时虽然我们指定的周期为 2 s,但是因为任务的运行就需要 3 s(超过周期),所以这种情况下 scheduleAtFixedRate 的处理方式为 上一次任务刚完成,则紧接着立即运行下一次任务,而不是使用线程池中的空闲线程来运行任务以维护 2 秒这个周期 —— 由此可见,每个定时任务在 ScheduledThreadPoolExecutor 中,都是串行运行的,即下一次运行任务一定在上一次任务结束之后。
如果某一个线程出现异常则丢弃。
如果定时任务数量超过 初始线程数量,因为小根堆的原因,leader-follower模式。
注意: DelayQueue是无界队列(maximumPoolSize参数无效);DelayQueue内部封装了一个PriorityQueue,它会根据time的先后时间排序,若time相同则根据sequenceNumber排序
参考: https://blog.csdn.net/xueguchen/article/details/117550455
package com.wxl.example.timer;
import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class ScheduleThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
// Executors.newSingleThreadExecutor();
for (int i = 0; i<3; i++) {
Runnable task = new Task("task" + i);
// scheduledExecutorService.schedule(task, 2L, TimeUnit.SECONDS); //执行一次
scheduledExecutorService.scheduleAtFixedRate(task, 0,2, TimeUnit.SECONDS);
// 可以看到此时虽然我们指定的周期为 2 s,但是因为任务的运行就需要 3 s(超过周期),
// 所以这种情况下 scheduleAtFixedRate 的处理方式为 上一次任务刚完成,则紧接着立即运行下一次任务,
// 而不是使用线程池中的空闲线程来运行任务以维护 2 秒这个周期 —— 由此可见,每个定时任务在 ScheduledThreadPoolExecutor 中,
// 都是串行运行的,即下一次运行任务一定在上一次任务结束之后。
}
}
}
class Task implements Runnable {
private String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
try{
System.out.println("name=" + name + ",startTime=" + new Date());
Thread.sleep(3000);
System.out.println("name=" + name + ",endTime=" + new Date());
}catch(Exception e){
e.printStackTrace();
}
}
}
2、Leader-Follower模式
多线程网络模型:
线程会有三种身份中的一种:leader和follower,以及一个干活中的状态:proccesser
基本原则:永远最多只有一个leader,而所有follower都在等待成为leader。
线程池启动时会自动产生一个Leader负责等待网络IO事件,当有一个事件产生时,Leader线程首先通知一个Follower线程将其提拔为新的Leader,然后自己执行这个任务,去处理这个网络事件,处理完毕后加入Follower线程等待队列,等待下次成为Leader。
这种方法可以增强CPU高速缓存相似性,及消除动态内存分配和线程间的数据交换。
3.ScheduledThreadPoolExecutor与Timer的区别
- Timer是单线程模式,ScheduledThreadPoolExecutor是多线程模式,并且重用线程池
- Timer调度是基于操作系统的绝对时间的,ScheduledThreadPoolExecutor调度是基于相对时间的,不受操作系统时间改变的影响
- Timer不会捕获TimerTask抛出的异常,加上Timer又是单线程的。一旦某个调度任务出现异常,则整个线程就会终止,其他需要调度的任务也不再执行。ScheduledThreadPoolExecutor基于线程池来实现调度功能,某个任务抛出异常后,其他任务仍能正常执行。
- Timer中执行的TimerTask任务整体上没有优先级的概念,只是按照系统的绝对时间来执行任务。ScheduledThreadPoolExecutor距离下次执行的时间间隔短的任务的优先级比较高
- Timer不支持对任务的排序。ScheduledThreadPoolExecutor为需要调度的每个任务按照距离下次执行时间间隔的大小来排序
- Timer无法从TimerTask中获取返回的结果。ScheduledThreadPoolExecutor中FutureTask类,能够通过Future来获取返回的结果。
定时任务框架-quartz
1、使用简介
2、各组件介绍
3、组件关系架构分析
4、监听器及插件
5、springboot整合quartz
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>${quartz.version}</version>
</dependency>
<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz-jobs</artifactId>
<version>${quartz.version}</version>
</dependency>
@RequestMapping("/test")
public void test() {
TriggerKey triggerKey = TriggerKey.triggerKey("trigger1", "trigger1");
//确保触发器在调度器里是唯一的
if (scheduler == null) {
System.out.println("注入失败");
return;
}
try {
Trigger trigger = scheduler.getTrigger(triggerKey);
if (trigger == null) {
trigger = TriggerBuilder.newTrigger()
.withIdentity(triggerKey)
.withSchedule(CronScheduleBuilder.cronSchedule("0/3 * * * * ?"))
.build();
}
JobDetail jobDetail = JobBuilder.newJob(QuartzJob.class)
.withIdentity("job1","job1")
.usingJobData("name", "测试调度")
.build();
scheduler.scheduleJob(jobDetail,trigger);
scheduler.start();
} catch (SchedulerException e) {
e.printStackTrace();
}
}