有一个以固定速率60秒执行的任务,但是该任务的执行时间超过了60秒,xxljob依然会以60秒的固定评率调度该任务,从而导致大量同时执行中的任务。
但是JDK中的ScheduledThreadPoolExecutor不会有这种问题。
ScheduledThreadPoolExecutor是如何实现的,如何避免了这个问题?
ScheduledThreadPoolExecutor的构造函数中使用了DelayedWorkQueue作为队列的实现。
DelayedWorkQueue是一个基于堆的数据结构,最近执行时间的任务在队列的头部。
DelayedWorkQueue被设计为在队列头部进行等待。当一个线程成为领导者时,它只等待下一个延迟时间结束,而其他线程则无限期地等待。领导者线程必须在调用 take() 或 poll(...) 返回之前向其他线程发出信号,除非在此期间有其他线程成为领导者。每当队列头部被一个具有更早过期时间的任务替换时,领导者字段将被重置为 null 而失效,并且会向某个等待的线程(但不一定是当前的领导者)发出信号。因此,等待的线程必须做好准备,在等待期间获得和失去领导者地位。
通过take或者poll获得任务时,会从头部移除任务,如果是周期任务,在任务执行完成后重新将任务加入队列中。
ScheduledThreadPoolExecutor中的任务被装饰成ScheduledFutureTask,其run方法:
public void run() {boolean periodic = isPeriodic();if (!canRunInCurrentRunState(periodic))cancel(false);else if (!periodic)ScheduledFutureTask.super.run();else if (ScheduledFutureTask.super.runAndReset()) {//如果是周期任务,重新计算下一次运行时间,并将任务重新加入队列。setNextRunTime();reExecutePeriodic(outerTask);}
}
以上设计避免了周期任务重复执行的问题。