https://www.jianshu.com/p/18f4c95aca24
流程:
1 提交任务的时候,任务被包装成ScheduledFutureTask对象加入延迟队列并启动一个woker线程。
2 用户提交的任务加入延迟队列时,会按照执行时间进行排列,也就是说队列头的任务是需要最早执行的。而woker线程会从延迟队列中获取任务,如果已经到了任务的执行时间,则开始执行。否则阻塞等待剩余延迟时间后再尝试获取任务。
3 任务执行完成以后,如果该任务是一个需要周期性反复执行的任务,则计算好下次执行的时间后会重新加入到延迟队列中。
特性:
1 使用DelayedWorkQueue作为阻塞队列,并没有像ThreadPoolExecutor类一样开放给用户进行自定义设置。该队列是ScheduledThreadPoolExecutor类的核心组件,后面详细介绍。
2 这里没有向用户开放maximumPoolSize的设置,原因是DelayedWorkQueue中的元素在大于初始容量16时,会进行扩容,也就是说队列不会装满,maximumPoolSize参数即使设置了也不会生效。
3 worker线程没有回收时间,原因跟第2点一样,因为不会触发回收操作。所以这里的线程存活时间都设置为0。
继承了 ThreadPoolExecutor 实现了 ScheduledExecutorService 接口
scheduleAtFixedRate 方法
代码块
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
ScheduledFutureTask<Void> sft =
new ScheduledFutureTask<Void>(command,
null,
triggerTime(initialDelay, unit),
unit.toNanos(period));
//然后在调用decorateTask进行包装,该方法是留给用户去扩展的,默认是个空方法
RunnableScheduledFuture<Void> t = decorateTask(command, sft);
sft.outerTask = t;
//延迟执行
delayedExecute(t);
return t;
}
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
//这里是增加一个worker线程,避免提交的任务没有worker去执行
//原因就是该类没有像ThreadPoolExecutor一样,woker满了才放入队列
ensurePrestart();
}
}
线程启动后,由ScheduledThreadPoolExecutor的父类ThreadPoolExecutor接管。
ensurePrestart:
代码块
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
if (wc < corePoolSize)
// 增加一个线程并启动
addWorker(null, true);
else if (wc == 0)
addWorker(null, false);
}
addWorker方法 会调用新创建线程的 start方法
线程获得cpu时间片后 执行线程的run方法 调用runworker方法
代码块
public void run() {
runWorker(this);
}
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask; // task为null
w.firstTask = null;
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 执行getTask()方法从延迟队列中获得任务
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 {
// 执行run()方法
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);
}
}
ScheduledFutureTask 内部类 中的run方法:
代码块
// 重写run 方法 实现 到达周期后 任务入队
public void run() {
boolean periodic = isPeriodic();
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
ScheduledFutureTask.super.run();
// runAndReset执行最初设置的Runnable代码,若代码成功执行,则返回true,否则返回false(runnable中的代码拋出异常)。
// 而只有当返回true时,执行reExecutePeriodic代码 把下次的任务添加进入队列
else if (ScheduledFutureTask.super.runAndReset()) {
// 设置下次执行时间
setNextRunTime();
// 任务重新入队
reExecutePeriodic(outerTask);
}
}
下次任务执行时间依赖 上次任务执行耗时
任务执行中有一次 遇到异常 则线程死掉 后续也不会再往队列中增加任务
// 任务重新入队方法: reExecutePeriodic()
代码块
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
reExecutePeriodic 以及delayedExecute 均调用了super.getQueue().add(task)行代码,
ScheduledThreadPoolExecutor类在内部自己实现了一个基于堆数据结构的延迟队列。add方法最终会落到offer方法中
代码块
public boolean add(Runnable e) {
return offer(e);
}
public boolean offer(Runnable x) {
// 参数校验
if (x == null)
throw new NullPointerException();
// 任务强转为RunnableScheduledFuture
RunnableScheduledFuture<?> e = (RunnableScheduledFuture<?>)x;
final ReentrantLock lock = this.lock; // 写队列加锁
lock.lock();
try {
int i = size;
// 队列大小不足 扩容
if (i >= queue.length)
grow();
// 更新队列大小
size = i + 1;
// 如果当前队列中无任务 直接加入队列头 无需调整
if (i == 0) {
// 任务加入队列头
queue[0] = e;
// 记录索引 用于加速取消任务?
setIndex(e, 0);
} else {
//把任务加入堆中,并调整堆结构,这里就会根据任务的触发时间排列
//把需要最早执行的任务放在前面
siftUp(i, e);
}
//如果新加入的元素就是队列头,这里有两种情况
//1.这是用户提交的第一个任务
//2.新任务进行堆调整以后,排在队列头
if (queue[0] == e) {
//这个变量起优化作用,后面说
leader = null;
//加入元素以后,唤醒worker线程
available.signal();
}
} finally {
lock.unlock();
}
return true;
}
runWorker()中调用的getTask() 方法:
代码块
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;
// 线程数目 超过最大线程数 或 ((超过核心线程数 或 允许核心线程超时)且已超时 且 (线程数>1 或队列为空) ) 执行回收线程
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;
}
}
}
workQueue.take();: 延迟队列中的take 方法
代码块
public RunnableScheduledFuture<?> take() throws InterruptedException {
final ReentrantLock lock = this.lock;
lock.lockInterruptibly();
try {
// 循环重试
for (;;) {
// 仅仅获取队头元素 不出队列
RunnableScheduledFuture<?> first = queue[0];
if (first == null)
// 队列中任务为空 执行await() 阻塞等待 await() 会释放锁资源
available.await();
else {
//成功拿到任务
//计算任务执行时间,这个delay是 任务触发时间 减去当前时间
long delay = first.getDelay(NANOSECONDS);
// 到了触发时间,则执行出队操作
if (delay <= 0)
return finishPoll(first);
first = null; // don't retain ref while waiting
//这里表示该任务已经分配给了其他线程,当前线程等待唤醒就可以 其他线程拿到最新任务正在执行作为leader
if (leader != null)
available.await();
else {
//否则把给任务分配给当前线程
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
//当前线程等待任务剩余延迟时间
available.awaitNanos(delay);
} finally {
//这里线程醒来以后,什么时候leader会发生变化呢?
//就是上面的添加任务的时候
// 重置leader 重新循环拿任务
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
//如果队列不为空,则唤醒其他woker线程
if (leader == null && queue[0] != null)
available.signal();
lock.unlock();
}
}
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
leader 变量优化 多个线程执行任务时的等待逻辑
这里为什么会加入一个leader变量来分配阻塞队列中的任务呢?原因是要减少不必要的时间等待。
比如说现在队列中的第一个任务1分钟后执行,那么用户提交新的任务时会不断的加入woker线程,如果新提交的任务都排在队列后面,
也就是说新的woker现在都会取出这第一个任务进行执行延迟时间的等待,当该任务到触发时间时,会唤醒很多woker线程,这显然是没有必要的。