9.10 定时任务框架初步了解
阅读框架实现方式,有助于更好的使用框架,理解设计思想。
一、基本使用
了解定时任务的基本使用
1.1 引入框架源码
1.2 调用SchedulerManager创建定时任务
该类是定时任务的线程管理工具,可通过此类提供的方法,创建定时任务,并加入线程队列
schedulerManager.schedule("activitybegin-job", "0 0/5 * * * ?", new ScheduledJob() {
@Override
private void run(){}; // 业务代码
@Override
private void terminate(){}; // 终止线程方式,应用于线程引擎终止时每个线程的销毁方式
}
调用schedulerManager.schedule方法创建一个定时任务线程,该方法需要传递三个参数
- jobName:定时任务的名称,后缀要与配置的job.type一致(application.properties)
- crontab表达式:周期执行模式
- ScheduleJob:线程任务(在定时任务线程中被执行),内部需要实现run、terminate两个方法,run内写业务代码、terminate内写终止工作线程方式
1.3 crontab表达式
通过书写crontab表达式,可以指定线程周期执行的模式,比如每隔5秒执行一次,每个小时的前五分钟的每5秒执行一次……
如上 “0 0/5 * * * ?” 代表的是每星期的每一天,每隔五分钟执行一次。
crontab表达式由一组连续的数字或字符组成,他们之间通过一个空格分隔。从左到右依次代表
秒 分钟 小时 天 月份 每周的第几天 年(可以省略)
以下是标识范围的的字符形式,0/x表示的是间隔,执行周期为x。 * 代表所有的时间段。 - 代表在什么什么范围内。?代表任意。
二、整体分析:类结构分析
理解类结构,是理解框架细节的前提。
- ScheduledJob:线程任务接口,我们的定时任务业务就是写在这个实现类里
- Scheduler:定时任务引擎,它包含一个内部类ScheduledJobThread,作为我们线程任务执行的载体。内部维护了一个ScheduledJobThread集合。
- SchedulerManager:根据配置,决定是否初始化Scheduler、但前定时任务线程的名字是否合法。它是我们直接操作的对象,可以通过它来创建定时任务。
- JobConfig:配置类,用于获取job前缀的配置属性。配置属性包括type:工作类型,whiteList:白名单。
- CronExpression:定时异常类。
三、具体分析:框架逻辑分析
按照程序执行流程,来理解框架的逻辑。
3.1 SchedulerManager
内部维护的属性:
- JobConfig:配置信息
- EnvironmentService:开发环境service,用于判断当前开发环境。
- mqEnabled:是否开启消息队列
- Scheduler:定时任务引擎
初始化动作:
@PostConstruct
public void init()
{
boolean jobSchedule = "job".contentEquals(config.getType());
if (jobSchedule)
{
instance = new Scheduler();
L.warn("Enable job-executing");
}
mqEnabled = jobSchedule;
if (mqEnabled)
{
if (CollectionUtils.isNotEmpty(config.getWhitelist()) && config.getWhitelist().contains("!mq"))
{
mqEnabled = false;
}
}
}
- 判断配置中定义的工作类型是否为“job”
- 如果是,创建Schedule对象
- 如果白名单不为空,且白名单中包含**!mq**则不开启消息队列,默认是开启的
schedule
- 如果初始化没有创建Scheduler,即配置中定义的工作类型不为“job",则直接返回。
- 判断jobname是否合法,(如果白名单为空,则所有名字都合法;如果白名单不为空,截取.或_前面的内容为module,如果module还包含._-则是一个错误的jobname,查看module是否在白名单中,如果不在就不合法。)
- 如果名字合法,则调用内部维护的Scheduler的schedule方法,将该定时任务载入线程中执行。
- 此处还有一个判断,如果当前环境是开发环境,默认的使用每五秒执行一次策略,其他环境的话按照我们传递的crontab执行。
3.2 Scheduler
内部维护属性
- List:定时任务线程集合
- stopped:终止所有线程标识。
初始化动作
public Scheduler()
{
Runtime.getRuntime().addShutdownHook(new Thread("destroy") {
public void run()
{
shutdown();
}
});
}
销毁动作(优雅解决)
创建一个名为destroy(销毁)的线程,先调用所有定时任务自己的销毁方式。并释放所有当前正在等待执行的线程的锁,并通过join让所有线程依次执行销毁。
private void shutdown()
{
synchronized (threads)
{
stopped = true;
for (ScheduledJobThread thread : threads)
{
thread.shutdown();
}
}
}
public void shutdown()
{
L.warn("[" + jobName + "] terminating");
stopped = true;
try
{
job.terminate();
} catch (Exception e)
{
LOG.error(null, e);
}
synchronized (this)
{
try
{
notify();
} catch (Exception e)
{
LOG.error(null, e);
}
}
try
{
join();
} catch (Exception e)
{
L.error(null, e);
}
LOG.warn("[" + jobName + "] is terminated");
}
}
schedule
创建ScheduledJobThread并将线程添加进入线程队列中(threads)启动线程(之前判断该线程引擎是否停止)
3.3 Scheduler -> ScheduledJobThread
初始化动作
- 修改线程名称为job_schedule
- 判断crontab表达式是否合法
run()
线程内部一直轮询执行job.run(也就是我们传递过来的定时任务业务),直到当前线程引擎终止。
while (!stopped)
{
try
{
Date lastCompletedTime = new Date();
waitUntilScheduled(lastCompletedTime);
if (stopped)
{
break;
}
job.run();
} catch (Throwable e)
{
LOG.error(null, e);
try
{
Thread.sleep(1000);
} catch (Throwable t)
{
// ignore
}
}
}
如果线程引擎没有停止,一直执行定时任务。
waitUntilScheduled()
private void waitUntilScheduled(Date afterTime)
{
if (stopped)
{
return;
}
Date scheduleTime = crontab.getNextValidTimeAfter(afterTime);
long delay = scheduleTime.getTime() - System.currentTimeMillis();
if (LOG.isInfoEnabled())
{
log("schedule at " + scheduleTime + ", delay " + TimeUnit.MILLISECONDS.toSeconds(delay) + "s");
}
if (delay <= 0)
{
return;
}
synchronized (this)
{
try
{
wait(delay);
} catch (InterruptedException e)
{
LOG.error(null, e);
}
}
}
通过crontab.getNextValidTimeAfter计算出下一次执行定时任务的时间。并减去当前时间获取需要间隔多久来执行下一次定时任务。
通过wait(delay)延期执行下一次任务。(锁的是当前线程)。
基本上每段代码之间都对于线程引擎运行状态进行了判断,最大程度控制了线程开关的准确性。