Preview
部分内容来源:《深入浅出Java多线程》 - 计划任务
前置知识:Java线程池原理,不了解的同学可以看这个: Java线程池执行与线程复用的原理
JDK版本:OpenJDK16.0.2
使用样例
将消息(包含发送时间)存储在数据库中,用一个定时任务,每隔1秒检查数据库在当前时间有没有需要发送的消息:
private static final ScheduledExecutorService executor =
new ScheduledThreadPoolExecutor(1, Executors.defaultThreadFactory());
private static SimpleDateFormat df = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args){
// 新建一个固定延迟时间的计划任务
// 新建任务1s以后,任务开始执行
// 上一个任务执行完以后,等待2s,执行下一个任务
System.err.printf("【%s】新建任务%n" , df.format(new Date()));
executor.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
if (haveMsgAtCurrentTime()) {
System.err.printf("【%s】大家注意了,我要发消息了%n" , df.format(new Date()));
}
}
}, 1, 2, TimeUnit.SECONDS);
}
public static boolean haveMsgAtCurrentTime(){
// 查询数据库,有没有当前时间需要发送的消息
// 这里省略实现,直接返回true
return true;
}
输出:
【2021-10-12 20:27:35】新建任务
【2021-10-12 20:27:36】大家注意了,我要发消息了
【2021-10-12 20:27:38】大家注意了,我要发消息了
【2021-10-12 20:27:40】大家注意了,我要发消息了
计划任务的特性
计划任务分为两种:
-
非周期性任务,这种任务只执行一次,需要在指定的时间运行
-
周期性任务,这种任务要执行多次,周期性任务又可以分为两种
- 固定频率:每隔一段时间,任务就执行一次,比如每五分钟执行一次
- 固定间隔:两次任务的执行之间需要间隔一定的时间,比如本次任务执行后,等待五分钟,然后执行下一次任务
假如让我们自己来实现一个计划任务线程池,我们需要实现两个特性:
- 多次执行任务
- 在指定时间执行任务
如果只执行非周期性任务,只需要满足第二点特性就可以,但对于周期性任务,必须两个特性都要满足,可以说,只要线程池可以实现这两个特性,这个线程池就是计划任务线程池
所以,ScheduledThreadPoolExecutor
的关键就在于,它是如何实现这两个特性的
下面,带着这两个疑问,我们来分析ScheduledThreadPoolExecutor
的源码
在分析过程中,我们顺着线程池的使用方式来阅读源码,首先看一下线程池在提交任务时会做些什么,然后再看看任务在执行时又会做些什么
ScheduledThreadPoolExecutor
类结构
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
// 计划任务线程池的构造方法之一
// 注意,这里使用的workQueue是DelayedWorkQueue,关于这个队列的具体内容,我们后面再聊
public ScheduledThreadPoolExecutor(int corePoolSize,ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory);
}
}
ScheduledThreadPoolExecutor
继承了ThreadPoolExecutor
,这个类就是线程池,不多赘述
ScheduledThreadPoolExecutor
还实现了ScheduledExecutorService
接口,这个接口规定了一些方法签名,这些方法负责把周期性任务提交到线程池,源码如下
public interface ScheduledExecutorService extends ExecutorService {
// 单次执行任务,无返回值
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit);
// 单次执行任务,有返回值
public <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit);
// 多次执行任务,创建任务后,经过 initialDelay 时间,执行第一次任务
// 此后,每隔 period 时间,执行一次任务,无论上一次任务是否完成,都会执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit);
// 多次执行任务,创建任务后,经过 initialDelay 时间,执行第一次任务
// 每次任务执行完成之后,间隔 delay 时间,才执行下一次任务
public ScheduledFuture<?> scheduleWithFixedDelay(Runnable command,
long initialDelay,
long delay,
TimeUnit unit);
}
提交任务的四个方法
ScheduledExecutorService
中制定了四个提交周期性任务,在ScheduledThreadPoolExecutor
中的实现如下:
schedule(无返回值)
public class ScheduledThreadPoolExecutor
extends ThreadPoolExecutor
implements ScheduledExecutorService {
// 用于打破调度关系的序列号,保证绑定项之间的FIFO顺序
private static final AtomicLong sequencer = new AtomicLong();
// 单次执行任务,无返回值
public ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
// decorateTask:直接返回第二个参数
// 在这里,会直接返回 new 出来的 ScheduledFutureTask 对象
RunnableScheduledFuture<Void> t = decorateTask(command,
// 创建任务,带有初始延时
new ScheduledFutureTask<Void>(command, null,
// triggerTime:根据delay、unit和当前系统时间,计算出第一次执行任务的时间
triggerTime(delay, unit),
// 序列号+1
sequencer.getAndIncrement()));
// 延期或周期性任务的主要方法
delayedExecute(t);
return t;
}
// 直接返回第二个参数
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
}
schedule(有返回值)
public <V> ScheduledFuture<V> schedule(Callable<V> callable,
long delay,
TimeUnit unit) {
if (callable == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<V> t = decorateTask(callable,
new ScheduledFutureTask<V>(callable,
triggerTime(delay, unit),
sequencer.getAndIncrement()));
delayedExecute(t);
return t;
}
scheduledAtFixedRate
// 多次执行任务,创建任务后,经过 initialDelay 时间,执行第一次任务
// 此后,每隔 period 时间,执行一次任务,无论上一次任务是否完成,都会执行
public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,
long initialDelay,
long period,
TimeUnit unit) {
if (command == null || unit &