ScheduledThreadPoolExecutor运作在ThreadPoolExecutor的基础上,自定义实现任务队列,每次take()拉取任务的时候,返回运行优先级最高的队首元素。
构造函数:将自定义的阻塞队列作为参数放到ScheduledThreadPoolExecutor里面。
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
源代码部分(take部分):
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
for (;;) {
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
available.await();
else {
long delay = first.getDelay(NANOSECONDS);
if (delay <= 0)
//返回任务
return finishPoll(first);
first = null; // don't retain ref while waiting
if (leader != null)
//如果有线程已经等待执行了,那么自己就await
available.await();
else {
//只能有一个线程延迟等待
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//线程延迟等待
available.awaitNanos(delay);
} finally {
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
这里的任务的获取是采用小根堆的算法,也就是一次排序能保证根节点的优先级最高,这里的delay越小,表示任务的运行优先级越高,如果小于0,那么这个任务表示已经过了执行时间,所以优先级最高,必须马上执行;如果优先级最高的任务的delay大于0,也就是任务的执行时间还没到,那么线程休眠delay时间。这里有一个leader进程,也就保证了只能一个线程去延迟等待,如果还有其他需要延迟的进程,那么就只能await等待。
在任务的执行函数体(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);
}
}
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p);
}
time表示下次执行时间,这里可以看出来,如果一个定时任务时每两秒执行一次,如果在此期间有优先级更高的任务堆积运行,那么当运行到该定时任务的时候,会很快执行,并不会与上一次执行相差两秒。
演示代码:
public static void main(String[] args) {
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(1);
for (int i = 0; i < 30; i++) {
final int index = i;
if (i == 0) {
executor.scheduleAtFixedRate(()->
System.out.println("现在时间 : " + System.currentTimeMillis() + " , 我在运行.")
,0,2, TimeUnit.SECONDS);
} else {
executor.execute(()->{
System.out.println("我不是延迟任务,我优先级最高,我是任务" + index);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
}
}
结果:
现在时间 : 1547182453546 , 我在运行.
我不是延迟任务,我优先级最高,我是任务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
现在时间 : 1547182468113 , 我在运行.
现在时间 : 1547182468113 , 我在运行.
现在时间 : 1547182468113 , 我在运行.
现在时间 : 1547182468113 , 我在运行.
现在时间 : 1547182468113 , 我在运行.
现在时间 : 1547182468113 , 我在运行.
现在时间 : 1547182468113 , 我在运行.
现在时间 : 1547182469540 , 我在运行.
现在时间 : 1547182471541 , 我在运行.
结果表明:每次运行完定时任务,设置的时间是上一次的time+period,所以如果上一次的时间加上延迟的时间还不能赶上现在的时间,那么这个任务还是看作是执行耽误的任务,将马上运行,所以就出现了上述的短时间内重复运行周期任务,直到赶上现在的时间,才会有延迟产生。