ScheduledThreadPoolExecutor前提与介绍
- ScheduledThreadPoolExecutor是java工具并发库提供的线程池之一。
- ScheduledThreadPoolExecutor主要提供的是线程任务延迟执行以及定时执行的服务。
前置知识
- 了解ThreadPoolExecutor。
- 了解FutureTask。
总体概括
- ScheduledThreadPoolExecutor
- ScheduledThreadPoolExecutor可以线程任务提供延迟执行和定时轮询的任务。
- ScheduledThreadPoolExecutor复用了父类ThreadPoolExecutor的线程状态,工作线程调度,拒绝策略等机制,减少重复的逻辑。
- ScheduledThreadPoolExecutor主要通过自己实现了阻塞队列DelayedWorkQueue,从而实现了延迟获取任务的效果。
- ScheduledThreadPoolExecutor把获取到的任务进行多一层的时间控制的包装,从而实现延迟执行或定时轮询的要求。
- ScheduledFutureTask
- ScheduledFutureTask继承了FutureTask类,拥有其开启多线程、运行、运行重置等方法。
- ScheduledFutureTask实现了RunnableScheduledFuture接口,拥有时间调度的功能。
- ScheduledFutureTask本质是在原任务上增加了延迟时间和轮询时间的控制属性,使之能被延迟阻塞队列正常调度,并实现对应的延迟功能和轮询功能。
- ScheduledFutureTask会维护一个执行时间属性,该属性是一个long类型数字,表示从某一个固定的时间点开始算起到执行时间相差的毫秒数。当前时间也可计算出对应的long值,通过比较可以判断出是否达到执行时间。
- DelayedWorkQueue
- DelayedWorkQueue是线程池中的任务阻塞队列,用于存放还未分配线程执行的任务。
- DelayedWorkQueue本质是一个基于数组的最小堆,其排序的依据是根据任务的执行时间点大小排序。
- 工作线程向DelayedWorkQueue获取任务时,若没有任务或者没有任务到达执行时间时都会返回空或者阻塞,因此获取到的任务一定是可以立刻执行的任务。
- DelayedWorkQueue可以接收所有实现了RunnableScheduledFuture接口的实现类(该接口是ScheduledFutureTask的接口之一),但个别方法例如remove,会对RunnableScheduledFuture做出针对性的优化,提高执行效率。
ScheduledThreadPoolExecutor属性和相关方法
/**
* 线程池关闭后是否继续执行定时任务
*/
private volatile boolean continueExistingPeriodicTasksAfterShutdown;
/**
* 线程池关闭后是否继续执行延迟任务
*/
private volatile boolean executeExistingDelayedTasksAfterShutdown = true;
/**
* 当任务被取消时是否要从队列中移除
*/
private volatile boolean removeOnCancel = false;
/**
* 用于判断任务是否能够在当前状态的线程池运行。
* 这里把任务分为两大类,定时轮询和一次性执行,两大类的任务都有对应的属性控制。
*
* 核心逻辑在ScheduledThreadPoolExecutor的父类ThreadPoolExecutor实现。
* 主要就是判断一下线程池是否是运行态,运行则返回true。
* 若是关闭态,则根据返回对应属性的布尔值。
* 若是其他状态则返回false。
* 关于ThreadPoolExecutor的五大状态可以先学习ThreadPoolExecutor。
*/
boolean canRunInCurrentRunState(boolean periodic) {
// 下面两个属性:continueExistingPeriodicTasksAfterShutdown和
// executeExistingDelayedTasksAfterShutdown
// 在线程池ScheduledThreadPoolExecutor统一配置。
return isRunningOrShutdown(periodic ?
continueExistingPeriodicTasksAfterShutdown :
executeExistingDelayedTasksAfterShutdown);
}
/*
下面四个方法都是该线程池的构造器
所有构造器都使用了线程池自身实现的延迟阻塞队列
*/
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), handler);
}
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
ScheduledThreadPoolExecutor内部类
ScheduledFutureTask 任务类
/**
* ScheduledFutureTask是ScheduledThreadPoolExecutor执行的最小单位。
* ScheduledFutureTask主要增加了延迟执行的相关属性,和定时执行的属性
* ScheduledThreadPoolExecutor会把获取到的任务包装成该类,再放入阻塞队列。
*
* ScheduledFutureTask类实现了Delayed接口,
* 顺序:RunnableScheduledFuture -> ScheduledFuture -> Delayed
* 该接口定义了方法getDelay用于判断任务距离时间还差多少毫秒。
*/
private class ScheduledFutureTask<V>
extends FutureTask<V> implements RunnableScheduledFuture<V> {
/**
* 序列号
* 该序列号由ScheduledThreadPoolExecutor的全局生成器生成。
* 由于序列号生成器是并发安全的,因此同一个个线程池不可能存在两个相同序列号任务。
* 序列号通常是任务创建的顺序,任务越早创建序列号越小。
* 序列号在比较任务优先级时用到,如果两个任务都到了执行时间,
* 则序列号越小,优先级越高。
*/
private final long sequenceNumber;
/**
* 任务执行的时间戳
* 若当前任务时间戳小于当前系统时间的时间戳时,
* 则表示到了该任务的执行时间。
*/
private long time;
/**
* 定时周期
* 该值记录第一次任务执行完毕后,
* 每过多少毫秒则执行任务一次。
*/
private final long period;
/**
* 实际入队的队列
* 这里是为子类留下了一个接口,可以在当前任务中代理另一个任务
* 当前类并没有用到该属性。
*/
RunnableScheduledFuture<V> outerTask = this;
/**
* 在阻塞队列中的属性
* ScheduledThreadPoolExecutor专门为自身实现了一个配套的阻塞队列。
* 该阻塞队列是一个基于数组的极小堆,该值保存当前任务在堆中的索引。
* 可以在堆中寻找当前任务时快速定位。
*/
int heapIndex;
/* 下面是三个构造器 */
// 带延迟的任务
ScheduledFutureTask(Runnable r, V result, long ns) {
super(r, result);
this.time = ns;
this.period = 0;
this.sequenceNumber = sequencer.getAndIncrement();
}
// 带延迟且定时轮询的任务
ScheduledFutureTask(Runnable r, V result, long ns, long period) {
// 省略,跟上面基本一样
}
// 但延迟且有返回值的任务
ScheduledFutureTask(Callable<V> callable, long ns) {
// 省略,跟上面基本一样
}
/**
* 当前任务的启动方法。
* 主要分为执行延迟任务和定时任务
* 延迟任务执行一次则任务设为完成标志。
* 定时任务执行后并不会设为完成标志,可以被多次调用。
*/
public void run() {
// 是否是定时任务,主要看定时时间是否不为0
boolean periodic = isPeriodic();
// 判断线程池是否处于工作状态
if (!canRunInCurrentRunState(periodic))
cancel(false);
else if (!periodic)
// 若不是定时任务,则run一次。
ScheduledFutureTask.super.run();
else if (ScheduledFutureTask.super.runAndReset()) { // 执行并重置
// 执行成功则设置下次执行时间
setNextRunTime();
// 再次判断线程池的状态是否可以执行该任务
// 若可以则确保有工作线程在执行
// (工作线程是ThreadPoolExecutor的概念)
reExecutePeriodic(outerTask);
}
}
/**
* 取消当前任务
* @param mayInterruptIfRunning 是否要中断当前任务
*/
public boolean cancel(boolean mayInterruptIfRunning) {
// 通过FutureTask父类方法,把任务取消
boolean cancelled = super.cancel(mayInterruptIfRunning);
// 如果取消成功,且如果取消移除标志被开启,而当前任务又在队列中
if (cancelled && removeOnCancel && heapIndex >= 0)
remove(this); // 从阻塞队列中移除当前任务
return cancelled;
}
/**
* 由于阻塞队列是最小堆,因此需要比较两个任务的优先级。
* 距离执行时间近的任务排在最前面。
* 这里ScheduledFutureTask并没有实现Comparable接口,
* 因此该方法也不是重写方法。
* 阻塞队列是专门为ScheduledFutureTask而定制,
* 因此也会调用该方法进行比较。
*/
public int compareTo(Delayed other) {
if (other == this) // compare zero if same object
return 0;
if (other instanceof ScheduledFutureTask) {
ScheduledFutureTask<?> x = (ScheduledFutureTask<?>)other;
// 比较执行时间,小的先执行。
long diff = time - x.time;
if (diff < 0)
return -1;
else if (diff > 0)
return 1;
// 执行时间相等,则比较序列号。
else if (sequenceNumber < x.sequenceNumber)
return -1;
else
return 1;
}
// 比较距离执行的时间,小的优先级高。
long diff = getDelay(NANOSECONDS) - other.getDelay(NANOSECONDS);
return (diff < 0) ? -1 : (diff > 0) ? 1 : 0;
}
/**
* 获取任务执行时间和当前时间的差值
* 用于判断任务是否到了执行时间
* 若当前时间大于任务的执行时间,则已到达。
*/
public long getDelay(TimeUnit unit) {
return unit.convert(time - now(), NANOSECONDS);
}
/**
* 该任务是不是定时轮询任务
*/
public boolean isPeriodic() {
return period != 0;
}
/**
* 设置下一次运行的时间
*/
private void setNextRunTime() {
long p = period;
if (p > 0)
time += p;
else
time = triggerTime(-p); // 对p做了限制,防止溢出
}
}
DelayedWorkQueue 阻塞队列
- 说明:阻塞队列只是作为线程池的一个工具,这里只展示几个核心方法,细节逻辑代码不作展示。
阻塞队列的属性
/**
* 阻塞延迟队列,基于数组的最小堆
* 需要取出任务时,则先判断堆顶的任务是否到达执行时间,
* 到达则取出,否则返回null。
*/
static class DelayedWorkQueue extends AbstractQueue<Runnable>
implements BlockingQueue<Runnable> {
// 初始数组大小
private static final int INITIAL_CAPACITY = 16;
// 基于数组
private RunnableScheduledFuture<?>[] queue =
new RunnableScheduledFuture<?>[INITIAL_CAPACITY];
// 锁
private final ReentrantLock lock = new ReentrantLock();
// 元素数量
private int size = 0;
// 如果队列为空,该值保存第一个等待获取任务的线程
// 若超过一个线程排队,则leader线程先取得任务,并唤醒其他线程
// 其他线程判断是否还有任务,若还有则继续领取任务,否则再次竞争leader位。
private Thread leader = null;
// 锁状态
private final Condition available = lock.newCondition();
/* ··· 下面是方法的代码,下文有核心方法的讲解。 ··· */
}
阻塞队列的offer方法
/**
* 插入任务
*/
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;
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;
}
阻塞队列的poll方法
/**
* 取出任务,不会阻塞
*/
public RunnableScheduledFuture<?> poll() {
final ReentrantLock lock = this.lock;
lock.lock(); // 上锁
try {
RunnableScheduledFuture<?> first = queue[0];
// 若阻塞队列没有任务,或者没有到达执行时间的任务
if (first == null || first.getDelay(NANOSECONDS) > 0)
return null;
else
return finishPoll(first); // 取出任务,并维持堆性质。
} finally {
lock.unlock(); // 解锁
}
}
阻塞队列的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;
// 判断leader位是否被占据
if (leader != null)
// 若被占据则无限期堵塞,直到被唤醒
available.await();
else {
// 运行到这表示leader位没有被占据
Thread thisThread = Thread.currentThread();
leader = thisThread;
try {
// 超时阻塞,delay是距离执行时间的毫秒数
available.awaitNanos(delay);
} finally {
// 运行到这表示当前线程被唤醒。
if (leader == thisThread)
leader = null;
}
}
}
}
} finally {
if (leader == null && queue[0] != null)
available.signal(); //唤醒可能被阻塞线程
lock.unlock(); // 解锁
}
}
阻塞队列的remove方法
/**
* 把任务从阻塞队列移除。
* 该方法有针对ScheduledFutureTask进行优化,加快删除的速度。
*/
public boolean remove(Object x) {
final ReentrantLock lock = this.lock;
lock.lock(); // 上锁
try {
// 获取任务x在队列中的索引。
int i = indexOf(x);
if (i < 0) // i< 0 表示任务不在堆中
return false;
// 把任务中的对索引属性设为-1
// 只有对ScheduledFutureTask有效。
setIndex(queue[i], -1);
// 下面逻辑是堆删除元素的逻辑
// 大概就是把堆最后一个元素放到要删除的元素的位置
// 然后从那个元素开始做siftDown操作
int s = --size;
RunnableScheduledFuture<?> replacement = queue[s];
queue[s] = null;
if (s != i) {
siftDown(i, replacement);
if (queue[i] == replacement)
siftUp(i, replacement);
}
return true;
} finally {
lock.unlock(); // 解锁
}
}
ScheduledThreadPoolExecutor核心逻辑追踪
添加任务逻辑
- execute方法 & submit方法
- schedule方法
- delayedExecute方法
- decorateTask方法
- schedule方法
execute方法 & submit方法
ScheduledThreadPoolExecutor对外执行任务的方法有2个,分别是execute方法和submit方法。这两个方法核心都是调用
ScheduledThreadPoolExecutor的schedule方法。
public void execute(Runnable command) {
schedule(command, 0, NANOSECONDS);
}
public Future<?> submit(Runnable task) {
return schedule(task, 0, NANOSECONDS);
}
public <T> Future<T> submit(Runnable task, T result) {
return schedule(Executors.callable(task, result), 0, NANOSECONDS);
}
public <T> Future<T> submit(Callable<T> task) {
return schedule(task, 0, NANOSECONDS);
}
schedule方法
schedule也根据传入的参数不同重写了多个对应的方法,但每个方法的逻辑基本一致,都是包装成 RunnableScheduledFuture再调用delayedExecute方法。
/**
* 核心调度方法
* 该方法把核心任务包装成调度任务,
* 并加入到阻塞队列,等待被执行。
*/
public ScheduledFuture<?> schedule(Runnable command,
long delay, TimeUnit unit) {
if (command == null || unit == null)
throw new NullPointerException();
// 这里先对本身任务Runable包装成ScheduledFutureTask类,
// 从而增加了对时间调度的管理信息。
// decorateTask是ScheduledThreadPoolExecutor保留的二级包装方法。
RunnableScheduledFuture<?> t = decorateTask(command,
new ScheduledFutureTask<Void>(command, null, triggerTime(delay, unit)));
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
// 该方法在ThreadPoolExecutor实现
// 主要功能是确保有工作线程轮询阻塞队列。
ensurePrestart();
}
}
decorateTask方法
/**
* 这个接口是ScheduledThreadPoolExecutor的保留方法。
* 它允许子类对RunnableScheduledFuture及任务本身做额外的处理或包装。
*/
protected <V> RunnableScheduledFuture<V> decorateTask(
Runnable runnable, RunnableScheduledFuture<V> task) {
return task;
}
核心调度逻辑
- 前面回顾整梳理
- reExecutePeriodic方法
- ensurePrestart方法
前面回顾整梳理
整理一下外部调用execute方法后,内部的调用流程:
- 外部调用ScheduledThreadPoolExecutor的execute方法,传入Runable任务。
- ScheduledThreadPoolExecutor直接把Runable交给了schedule方法。
- schedule方法把Runable任务包装成ScheduledFutureTask类,即调度任务,该类增加了延迟执行的时间和定时执行的时间。
- schedule方法把调度任务交给了delayedExecute方法。
- delayedExecute把调度任务放入到延迟阻塞队列,并调用了ensurePrestart方法。
- ensurePrestart会激活工作线程从队列获取任务。
- 工作线程获取到任务后会调用任务的run方法,即执行调度任务。
- 调度任务执行run时判断当前任务是定时轮询任务还是一次性任务
- 若是一次性任务则直接执行 FutureTask 的run方法,并把任务置为完成状态。
- 若是定时轮询任务则执行 FutureTask 的runAndReset方法,重新计算执行时间,并调用ScheduledThreadPoolExecutor 的reExecutePeriodic 方法进入下一次轮询的等待。
reExecutePeriodic方法
/**
* 重置定时任务
* 该方法把执行过后的定时轮询任务重新加入阻塞队列
*/
void reExecutePeriodic(RunnableScheduledFuture<?> task) {
// 判断当前任务是否可以运行
if (canRunInCurrentRunState(true)) {
super.getQueue().add(task);
if (!canRunInCurrentRunState(true) && remove(task))
task.cancel(false);
else
ensurePrestart();
}
}
ensurePrestart方法
/**
* 该方法确保有工作线程在运行
* 该方法是ThreadPoolExecutor的方法,
* 但在ThreadPoolExecutor中并没有被使用到。
*/
void ensurePrestart() {
int wc = workerCountOf(ctl.get());
// 工作线程数小于核心线程数时
if (wc < corePoolSize)
// 添加一个核心工作线程
addWorker(null, true);
// 若核心线程数为0,且没有工作线程
else if (wc == 0)
// 则新建一个工作线程
addWorker(null, false);
}
关闭线程池逻辑
- shutdown方法
- onShutdown方法
- shutdownNow方法
shutdown方法
/**
* 关闭线程池
*/
public void shutdown() {
// 调用父类ThreadPoolExecutor的关闭线程池方法
super.shutdown();
}
/**
* 这是ThreadPoolExecutor的shutdown方法。
* 这里引用自ThreadPoolExecutor的教程
* 详细信息可以先学习ThreadPoolExecutor。
*
* ThreadPoolExecutor外部通过调用该方法关闭线程池
* 该方法被执行后,线程池状态被设为SHUTDOWN,
* 该状态下线程池不再接收新的任务,但会继续执行队列中的任务,
* 直到全部执行完毕。
*/
public void shutdown() {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess(); // 确定有关闭线程的权限
advanceRunState(SHUTDOWN); // 设为关闭状态
interruptIdleWorkers(); // 中断所有空闲工作线程
onShutdown(); // 空方法,该方法被当前线程池重写了
} finally {
mainLock.unlock();
}
tryTerminate();
}
onShutdown方法
/**
* 根据线程池策略清空一次性任务和定时轮询任务。
*/
@Override
void onShutdown() {
BlockingQueue<Runnable> q = super.getQueue();
// 获取策略
boolean keepDelayed =
getExecuteExistingDelayedTasksAfterShutdownPolicy();
boolean keepPeriodic =
getContinueExistingPeriodicTasksAfterShutdownPolicy();
if (!keepDelayed && !keepPeriodic) {
// 若线程池关闭后,不执行任何的任务
// 则轮询标离阻塞队列的所有任务,并全部取消
// 最后清空阻塞队列。
for (Object e : q.toArray())
if (e instanceof RunnableScheduledFuture<?>)
((RunnableScheduledFuture<?>) e).cancel(false);
q.clear();
}
else {
// 否则按照策略删除任务
for (Object e : q.toArray()) {
if (e instanceof RunnableScheduledFuture) {
RunnableScheduledFuture<?> t =
(RunnableScheduledFuture<?>)e;
// 若任务按照策略不被保留
// 或者任务已经被取消
if ((t.isPeriodic() ? !keepPeriodic : !keepDelayed) ||
t.isCancelled()) {
// 从队列移除,并取消任务。
// 任务被多次取消不会抛异常,不影响逻辑执行。
if (q.remove(t))
t.cancel(false);
}
}
}
}
// 尝试终止线程池
// 用于维护ThreadPoolExecutor的逻辑
// 只有所有任务都被删除线程池才会终止。
tryTerminate();
}
shutdownNow方法
/**
* 立刻停止线程池
*/
public List<Runnable> shutdownNow() {
return super.shutdownNow();
}
/**
* 这是ThreadPoolExecutor的shutdownNow方法。
* 这里引用自ThreadPoolExecutor的教程
* 详细信息可以先学习ThreadPoolExecutor。
*
* 立刻关闭线程池
* 把线程池状态设为STOP
* 所有工作线程无论是否活跃都会被中断。
*/
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
advanceRunState(STOP); // 设为STOP状态
interruptWorkers(); // 中断活跃状态下工作线程
tasks = drainQueue(); // 获取剩余未执行的任务
} finally {
mainLock.unlock();
}
// 终止线程池
// STOP状态下会尝试终止空闲线程
// 若所有工作线程都为空,则把线程池转化为终止态。
tryTerminate();
return tasks; // 返回没有执行的任务列表
}
Executors工厂类
ScheduledExecutorService 抽象调度服务
/**
* 抽象调度服务(门面模式)
* 该服务是Executors为所有调度类型的线程池抽象出来的服务接口,
* 从而可以为所有调度类型的线程池提供已给统一的接口,屏蔽不同线程池的差异。
*/
public interface ScheduledExecutorService extends ExecutorService {
public ScheduledFuture<?> schedule(
Runnable command, long delay, TimeUnit unit);
public <V> ScheduledFuture<V> schedule(
Callable<V> callable, long delay, TimeUnit unit);
public ScheduledFuture<?> scheduleAtFixedRate(
Runnable command, long initialDelay, long period, TimeUnit unit);
public ScheduledFuture<?> scheduleWithFixedDelay(
Runnable command, long initialDelay, long delay, TimeUnit unit);
}
DelegatedScheduledExecutorService调度服务代理类
/**
* ScheduledExecutorService实现代理类。
* 套上这层代理类可以阻止外部程序修改线程池的配置。
*/
static class DelegatedScheduledExecutorService
extends DelegatedExecutorService
implements ScheduledExecutorService {
private final ScheduledExecutorService e;
DelegatedScheduledExecutorService(ScheduledExecutorService executor) {
super(executor);
e = executor;
}
/*
观察该代理类实现的接口和构造器传入的参数类型,
容易发现传入进来的实体都实现了本类需要实现的方法。
而本类的确也是一个代理类,因此所有要实现的方法都是通过executor对应的方法实现。
不过本类也继承了DelegatedExecutorService,因此也获得了它所有的方法。
为了简洁,不再展示对应的方法。
*/
}
调度线程池
/**
* 生成一个调度线程池,指定核心线程的数量。
*/
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
/**
* 在上面基础上指定了对应的线程工厂。
*/
public static ScheduledExecutorService newScheduledThreadPool(
int corePoolSize, ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory);
}
调度串行线程池
/**
* 生成只有一个核心线程的调度线程池
* Executors还在ScheduledThreadPoolExecutor外部套上了一个代理类
* 使外部程序无论如何也无法修改线程池的配置。
*/
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1, threadFactory));
}
总结
- ScheduledThreadPoolExecutor通过ScheduledFutureTask 实现了对延迟时间的记录,通过DelayedWorkQueue 实现了对延迟时间的控制。
- ScheduledThreadPoolExecutor可以控制线程池关闭后轮询任务和延迟任务是否继续执行。
- DelayedWorkQueue 基于堆,增删查改时都会全局上锁,且队列长度理论上无限。
核心线程数不应当为零
- 原理:由于延迟队列的机制,即使任务队列存在任务都有可能返回空值。而ScheduledThreadPoolExecutor所依赖的ThreadPoolExecutor机制中,若线程不属于核心线程则在获取不到任务后会被释放。线程池在释放线程后如果发现工作线程数为0,而阻塞队列不为空,就会重新创建一个新的工作线程,而新的工作线程又会尝试去获取新任务...
- 假设:如果核心线程数设为0。
- 结果:因为核心线程数为0,则所有工作线程都不是核心线程,获取不到任务都会被释放。这时最后一个工作线程释放后,线程池会判断阻塞队列是否还有任务,如果有会立刻新建一个工作线程,但该线程获取任务时又为空,再次被释放,而线程池又会创建一个新的线程...如次一直循环,直到阻塞队列为空为止。而这个循环不断创建和销毁线程,开销及其庞大,应当被避免。