原文地址:《ScheduledThreadPoolExecutor实现原理解析》
1、什么是ScheduledThreadPoolExecutor
通熟的来就是一个线程池,只不过这个线程池中的所有工作线程需要处理的都是计划任务,这个任务分为两种,一种是重复任务,一种是一次性任务,所谓一次性任务,基本上是指定在提交任务多长时间之后开始执行,而重复性任务就是在任务被执行完成之后,在一定的时间间隔之后还会被执行,重复任务具体什么时候被执行主要与任务设置的执行时间间隔有关。而ScheuledThreadPoolExecutor主要是被设计用来维护和调度执行计划任务的线程资源以及任务队列中任务的调整。
2、ScheduledThreadPoolExecutor和ThreadPoolExecutor有什么区别
在线程调度方面,可以说两者没有本质的区别,在核心线程的添加、任务队列的已满后,非核心工作线程的创建以及任务队列满后的任务拒绝流程方面,两种线程池没有区别。但是在一下几个方面,ScheduledThreadPoolExecutor还是有别于ThreadPoolExecutor的。
1、拥有单独定义的任务类型,详细信息可以查看ScheduledFutureTask类代码
2、使用自定义的队列,这种队列没有大小限制,在添加任务时不会因为队列中任务达到指定的数量而阻塞任务的添加,所以说它是一个无限延迟队列(队列中存放的最大任务数量为Integer.MAX_VALUE)
3、支持在线程池关闭后,移除或取消已经不能够执行的任务
3、ScheduledThreadPoolExecutor运行流程以及实现原理
关于ScheduledThreadPoolExecutor的线程调度流程以及相关工作线程创建流程与ThreadPoolExecutor的流程一致,就不重复叙述了(不清楚的可查看《ThreadPoolExecutor的实现机制》)。首先根据实力代码分析任务的提交流程,代码如下:
System.out.println("commit task...."+",time:"+String.valueOf(System.currentTimeMillis()));
executorService.schedule(() ->{
System.out.println("start task...."+",time:"+String.valueOf(System.currentTimeMillis()));
closeThreadPool();
},10000L, TimeUnit.MILLISECONDS);
使用了schedule方法提交了任务,并制定了任务开始执行时间,为当前时间延迟10秒执行。
public ScheduledFuture<?> schedule(Runnable command,
long delay,
TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null,
triggerTime(delay, unit)));
delayedExecute(t);
return t;
}
使用封装方法,通过对command进行封装转换获取一个ScheduledFutureTask,其中任务内部包含一个time属性,time表明任务下次的执行时间。之后调用delayedExecute方法执行ScheduledFutureTask任务。
private void delayedExecute(RunnableScheduledFuture<?> task) { if (isShutdown()) reject(task); else { //调用队列方法,将当前任务添加至队列中 super.getQueue().add(task); if (isShutdown() && !canRunInCurrentRunState(task.isPeriodic()) && remove(task)) task.cancel(false); else ensurePrestart(); } }
//队列添加任务 public boolean offer(Runnable x) { if (x == null) throw new NullPointerException(); RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x; final ReentrantLock lock = this.lock; lock.lock(); try { int i = size; //判断当前队列长度是否满足,不满足则扩充 if (i >= queue.length) grow(); size = i + 1; //队列为空,则当前任务为队列的第一个任务,index为0 if (i == 0) { queue[0] = e; setIndex(e, 0); } else { //筛选任务,将任务按照执行时间顺序排列在队列中 siftUp(i, e); } if (queue[0] == e) { leader = null; available.signal(); } } finally { lock.unlock(); } return true; }
上述整个流程就是任务队列如何添加任务到队列中。现在开始执行工作线程的创建并从线程池中获取任务,任务执行的方法位于ThreadPoolExecutor类中,具体代码如下:
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
//获取需要执行的任务
while (task != null || (task = getTask()) != null) {
w.lock();
// If pool is stopping, ensure thread is interrupted;
// if not, ensure thread is not interrupted. This
// requires a recheck in second case to deal with
// shutdownNow race while clearing interrupt
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task);
Throwable thrown = null;
try {
task.run();
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown);
}
} finally {
task = null;
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
private Runnable getTask() { boolean timedOut = false; // Did the last poll() time out?
for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // Are workers subject to culling? // 是否配置的核心线程超时时间或者当前工作线程数大于核心线程数,即任务队列已满 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } } //poll方法获取待执行任务 public RunnableScheduledFuture<?> poll(long timeout, TimeUnit unit) throws InterruptedException { long nanos = unit.toNanos(timeout); final ReentrantLock lock = this.lock; lock.lockInterruptibly(); try { for (;;) { RunnableScheduledFuture<?> first = queue[0]; if (first == null) { if (nanos <= 0) return null; else nanos = available.awaitNanos(nanos); } else { //判断当前任务的延迟时间是否小于等于0,即当前任务是否到达执行时间,到达则 弹出任务,否则继续循环等待判断 long delay = first.getDelay(NANOSECONDS); if (delay <= 0) return finishPoll(first); if (nanos <= 0) return null; first = null; // don't retain ref while waiting if (nanos < delay || leader != null) nanos = available.awaitNanos(nanos); else { Thread thisThread = Thread.currentThread(); leader = thisThread; try { long timeLeft = available.awaitNanos(delay); nanos -= delay - timeLeft; } finally { if (leader == thisThread) leader = null; } } } } } finally { if (leader == null && queue[0] != null) available.signal(); lock.unlock(); } }
通过上述任务获取代码可以看出,在维护任务执行时间是由任务本省控制的,工作线程只要获取到线程就会执行,由于在获取任务过程中,通过一个死循环对队列中的第一个任务的time进行不断的判断,当time小于等于0是,开始正式弹出任务,而工作线程在这时才能获取到任务。由于在对任务执行时间的判断是通过循环来实现的,任务执行时间的判断存在时间差,任务弹出到任务真正可以执行也需要时间,这就导致了任务执行时间的不精准。
接下来的任务执行流程就与ThreadPoolExecutor中的任务执行流程类似,不作叙述了。
代码实例
代码如下:
package top.selfhelp;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author zhuyuqiang
*/
public class SchedulerApplication {
private static ScheduledThreadPoolExecutor executorService;
public static void main(String[] args){
executorService = new ScheduledThreadPoolExecutor(2);
System.out.println("commit task...."+",time:"+String.valueOf(System.currentTimeMillis()));
executorService.schedule(() ->{
System.out.println("start task...."+",time:"+String.valueOf(System.currentTimeMillis()));
closeThreadPool();
},10000L, TimeUnit.MILLISECONDS);
}
private static void closeThreadPool(){
if(executorService != null){
executorService.shutdownNow();
}
}
}
执行结果如下:
commit task….,time:1561990901777
start task….,time:1561990911827
任务开始时间与任务期望被执行时间存在毫秒级的误差。