场景
- 指定时间点执行任务
- 指定时间间隔执行任务
- 在二的基础上指定次数执行任务(场景较少)
线程轮询+Sleep
最简单的实现方法是启动一个线程,设定时间间隔或时间点,进入循环后,调用Sleep睡眠对应时间间隔,然后执行任务(放入线程池执行任务)1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25public class ScheduleThread extends Thread {
public ScheduleThread() {
super();
super.setDaemon(true);
}
public void run() {
try {
for (;;) {
logger.debug("Cache task schedule thread running.");
doRun();
Thread.sleep(CacheManager.scheduleInterval);
}
} catch (Exception x) {
logger.error("Shedule cache task exception.", x);
} finally {
try {
Thread.sleep(CacheManager.scheduleInterval);
} catch (InterruptedException e) {}
}
}
public void doRun() {
// if 间隔模式 放入线程池执行任务
// else if 时间点模式 获取任务,判断是否到达时间点,是则放入线程池执行任务
}
}
Timer & TimerTask
Timer内部维护一个TaskQueue和TaskThread守护线程,Timer实例化时启动守护线程,线程会轮询TaskQueue任务(这里与第一种方法类似),Timer接收TimerTask后放入TaskQueue,按执行时间排序,轮询逻辑里面使用了synchronized同步方法,Timer简单但相比第一种自定义方式,执行任务是单线程同步执行,任务执行时间会影响后面的任务,效率非常不好。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29public class MyTimerTask {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new MyTask1(), 2 * 1000); // 等待2秒后执行任务
timer.schedule(new MyTask3(), 5 * 1000, 3 * 1000); // 等待5秒后执行任务,每间隔3秒再执行任务
try {
SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
Date taskTm = df.parse("2018-05-28 23:51:00"); // 指定时间点执行任务
timer.schedule(new MyTask2(), taskTm);
} catch (ParseException e) {
System.out.println(e);
}
}
}
class MyTask1 extends TimerTask {
public void run() {
System.out.println(String.format("%s :run task 1", new Date().getTime()));
}
}
class MyTask2 extends TimerTask {
public void run() {
System.out.println(String.format("%s :run task 2", new Date().getTime()));
}
}
class MyTask3 extends TimerTask {
public void run() {
System.out.println(String.format("%s :run task 3", new Date().getTime()));
}
}
ScheduledExecutorService
JDK里面提供ScheduledExecutor改进了Timer,引入线程池执行调度任务,就是我们实现的第一种方法,jdk做了优化封装;
间隔时间模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26public class ScheduledExecutorTest {
public static void main(String[] args) {
ScheduledExecutorService service = Executors.newScheduledThreadPool(10); // 创建初始化10个线程的线程池
service.schedule(new Job1(), 1, TimeUnit.SECONDS); // 延迟1秒后执行
service.scheduleAtFixedRate(new Job2(), 1, 2, TimeUnit.SECONDS); // 延迟1秒后执行,然后每间隔2秒执行 (任务执行时间不影响)
service.scheduleWithFixedDelay(new Job3(), 1, 3, TimeUnit.SECONDS); // 延迟1秒后执行,执行完后再间隔3秒执行(该任务自身串行执行)
}
}
class Job1 implements Runnable {
public void run() {
System.out.println("execute job1");
}
}
class Job2 implements Runnable {
public void run() {
System.out.println("execute job2");
}
}
class Job3 implements Runnable {
public void run() {
System.out.println("execute job3");
}
}指定时间点模式
该模式没有直接实现,可以变通成,按时间点与当前时间对比做判断,设置需要延迟多久进行执行(schedule方法),如果是每周或每月,则需要计算最早执行时间点,然后用scheduleAtFixedRate或scheduleWithFixedDelay实现;
Quartz+java
ScheduledExecutorService的问题在于比较复杂的调度情况时候,逻辑复杂,线程池需要自己评估,调度配置硬编码耦合,所以有了任务调度框架,工作里面使用最多的就是Quartz
maven 坐标
1
2
3
4
5<dependency>
<groupId>org.quartz-scheduler</groupId>
<artifactId>quartz</artifactId>
<version>2.3.0</version>
</dependency>例子,cron方式可以把表达式独立到配置中
格式:秒 分 小时 月内日期 月 周内日期 年(optional)
“n/m” 字符:增量值, m时间循环一次
“?” 字符:月内日期和周内日期使用,表示这个位置忽略
“*” 字符:表示任意值
Cron 表达式在线工具1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22public class MyJob implements Job {
public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
System.out.println(new Date());
}
public static void main(String[] args) throws SchedulerException {
// job to jobDetail
JobDetail jobDetail = JobBuilder.newJob(MyJob.class).withIdentity("job1", "group1").build();
// trigger
Trigger trigger = TriggerBuilder.newTrigger().withIdentity("trigger1", "group")
.withSchedule(SimpleScheduleBuilder.simpleSchedule().withIntervalInSeconds(3).repeatForever())
.build();
Trigger trigger2 = TriggerBuilder.newTrigger().withIdentity("trigger2", "group")
.withSchedule(CronScheduleBuilder.cronSchedule("0 11 17 ? * MON-FRI"))
.build();
// scheduler
Scheduler scheduler = new StdSchedulerFactory().getScheduler();
// jobDetail trigger to scheduler
scheduler.scheduleJob(jobDetail, trigger2);
// start
scheduler.start();
}
}体系结构(网上找的图 )
1、Job和Trigger之间1对多关系
2、trigger不止上面两种,还有DateIntervalTrigger和NthIncludedDayTrigger
3、Job的类型:stateless、stateful(有状态只能串行执行)
4、Scheduler调度线程:常规调度线程(轮询trigger), misfired调度线程(轮询检查misfired trigger, 根据misfired策略处理)
5、Quartz 自带一个线程池的实现:SimpleThreadPool, 线程池大小会影响性能以及misfired,在配置中设置org.quartz.threadPool.class进行替换扩展
6、misfired策略
MISFIRE_INSTRUCTION_FIRE_ONCE_NOW:针对 misfired job 马上执行一次;
MISFIRE_INSTRUCTION_DO_NOTHING:忽略 misfired job,等待下次触发;
7、存储方式:RAMJobStore(基于内存,快),JobStoreSupport(持久化到数据库,实现优雅停机,导致速度变慢,由StdJDBCDelegate代理实现)1
2org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.isClustered = true
Quartz+Spring
1 | public class MyTask { |
1 | <!-- Job --> |
Quartz+Springboot
Job
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 禁止并发执行
public class MyJob extends QuartzJobBean {
private ConfigUtil configUtil;
protected void init(JobExecutionContext context) {
final JobDataMap jobDataMap = context.getMergedJobDataMap();
configUtil = (ConfigUtil) jobDataMap.get("configUtil");
}
protected void executeInternal(JobExecutionContext context) throws JobExecutionException {
init(context); // 利用context传入实例
// do something
}
}JobDetail、trigger、scheduler
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class MyJobConfig {
"${cron}") (
private String cron; // cron配置
private ConfigUtil configUtil;
"myJobDetailFactoryBean") (name =
public JobDetailFactoryBean firstJobDetailFactoryBean() {
JobDetailFactoryBean jobDetailFactory = new JobDetailFactoryBean();
jobDetailFactory.setJobClass(MyJob.class);
Map<String, Object> data = new HashMap<String, Object>();
data.put("configUtil", configUtil); // 传入其他实例
jobDetailFactory.setJobDataAsMap(data);
jobDetailFactory.setDurability(true);
return jobDetailFactory;
}
"myCronTriggerFactoryBean") (name =
public CronTriggerFactoryBean firstCronTriggerFactoryBean(
@Qualifier("myJobDetailFactoryBean") JobDetailFactoryBean jobDetailFactory) {
CronTriggerFactoryBean cronTriggerFactory = new CronTriggerFactoryBean();
cronTriggerFactory.setCronExpression(cron);
cronTriggerFactory.setJobDetail(jobDetailFactory.getObject());
return cronTriggerFactory;
}
"mySchedulerFactoryBean") (name =
public SchedulerFactoryBean schedulerFactoryBean(
@Qualifier("myJobDetailFactoryBean") JobDetailFactoryBean myJobDetail,
@Qualifier("myCronTriggerFactoryBean") CronTriggerFactoryBean myTrigger) {
SchedulerFactoryBean scheduler = new SchedulerFactoryBean();
scheduler.setJobDetails(myJobDetail.getObject());
scheduler.setTriggers(myTrigger.getObject()); // 可添加多个trigger
return scheduler;
}
}springboot 使用注解方式 FIXME
Spring schedule FIXME
Spring自己提供了一个schedule调度模块,配置更简单了,但听说性能不行
这里有个使用方法
心跳检查 & Timing Wheel
场景
- 网络编程的心跳逻辑,有netty的实现
- 大量定时任务进行超时判断,有kafka的实现
- 游戏里面也有大量的buff时间判断
- 剔除空闲连接
- 扩展:是否可以作为业务状态的超时判断?
链接
###