最近跟别人聊天,别人问我了一个问题ScheduledExecutorService的原理是啥呢?
说实话,一脸懵逼的就过去了,虽然知道这么个东西,说实话也没细看实现的原理,今天就来分析一下吧。
我们先从一段代码开始今天的旅程。
ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1);
这里创建一个线程池为1的调度任务
//1秒后开始启动,每隔3s执行一次service.scheduleAtFixedRate(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "我开始执行了"); }}, 1, 3, TimeUnit.SECONDS);
这里每隔3便会执行一次Runnable。
这便是调度任务服务的简单实用方法,下面来仔细的分析一下源码。
public ScheduledThreadPoolExecutor(int corePoolSize) { super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS, new DelayedWorkQueue());}
进入源码可以看到这里的队列用的是延迟队列的实现。
static class DelayedWorkQueue extends AbstractQueue implements BlockingQueue {
通过跟踪源码分析可知,其实底层用的还是BlockingQueue(源码下篇分析),哈哈,看到这里是不是就很清楚了呢?
下面我们在来看scheduleAtFixedRate 这个方法,源码如下:
/** * @throws RejectedExecutionException {@inheritDoc} * @throws NullPointerException {@inheritDoc} * @throws IllegalArgumentException {@inheritDoc} */public ScheduledFuture> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { if (command == null || unit == null) throw new NullPointerException(); if (period <= 0) throw new IllegalArgumentException(); ScheduledFutureTask sft = new ScheduledFutureTask(command, null, triggerTime(initialDelay, unit), unit.toNanos(period)); RunnableScheduledFuture t = decorateTask(command, sft); sft.outerTask = t; delayedExecute(t); return t;}
仅仅从代码分析来看,delayedExecute(t); 这个才是关键代码,其实方法的名称已经表明了,老外方法命名真规范,一眼就看出来要干啥。
两个判断条件我们就不说了,也没啥含量。
先来看sft这个对象的生成。先来分析一下参数的含义
第一个对象:Runnable对象,不用多说,
第二个对象:result,从Future可以看出,该任务执行可以携带返回值,不过这里传值是个null,可以不用关注
第三个对象:触发时间,从triggerTime可以看出是下次执行的时间
第四个对象:以纳秒计算的一个时间单位,也就是间隔时间。
sft对象生成之后,便开始了执行的过程。看到这里会有个疑问,任务到底是怎么执行的呢?
带着这个疑问,继续往下看delayedExecute这个方法。
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(); }}
关键部分是ensurePrestart,那我们直接进入这个方法。
void ensurePrestart() { int wc = workerCountOf(ctl.get()); if (wc < corePoolSize) addWorker(null, true); else if (wc == 0) addWorker(null, false);}
继续进入关键代码addWorker
private boolean addWorker(Runnable firstTask, boolean core) { retry: for (;;) { int c = ctl.get(); int rs = runStateOf(c); // Check if queue empty only if necessary. if (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty())) return false; for (;;) { int wc = workerCountOf(c); if (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize)) return false; if (compareAndIncrementWorkerCount(c)) break retry; c = ctl.get(); // Re-read ctl if (runStateOf(c) != rs) continue retry; // else CAS failed due to workerCount change; retry inner loop } } boolean workerStarted = false; boolean workerAdded = false; Worker w = null; try { w = new Worker(firstTask); final Thread t = w.thread; if (t != null) { final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { // Recheck while holding lock. // Back out on ThreadFactory failure or if // shut down before lock acquired. int rs = runStateOf(ctl.get()); if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true; } } finally { mainLock.unlock(); } if (workerAdded) { t.start(); workerStarted = true; } } } finally { if (! workerStarted) addWorkerFailed(w); } return workerStarted;}
疑问:线程的状态是通过什么标记的呢?

从897和898也基本能猜出来了,线程的状态是用AtomicInteger进行标记的。

其实这两个循环的目的只有一个,就是获取当前线程是否可执行。
关键代码如下:
if (compareAndIncrementWorkerCount(c)) break retry;
在这里可以看到,通过CAS计算之后,才会跳出该循环。
这里先放个疑问出来,如果前一次的任务还没有执行完,这里CPU会不会一直轮空?会不会造成cpu的飙升?
退出上面的循环后,实例化一个Worker 对象之后,进入了锁控制的部分,此处可以保证原子化。
if (rs < SHUTDOWN || (rs == SHUTDOWN && firstTask == null)) { if (t.isAlive()) // precheck that t is startable throw new IllegalThreadStateException(); workers.add(w); int s = workers.size(); if (s > largestPoolSize) largestPoolSize = s; workerAdded = true;}
从这里可以看出,增加一个worker对象的前提是线程的状态为:
rs < SHUTDOWN ||(rs == SHUTDOWN&& firstTask == null))
然后调用t.start();
此时该线程的任务开始执行。