属性
// ctl 本质就是一个int类型的数值
// 线程池初始状态为RUNNING
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
// Integer.SIZE = 32, COUNT_BITS = 29
// ctl 表述了两种状态:1.高3位标识线程池的状态;2.低29位标识线程池工作线程的个数
private static final int COUNT_BITS = Integer.SIZE - 3;
// 000111 11111111 11111111 11111111 工作线程的最大数量掩码
private static final int COUNT_MASK = (1 << COUNT_BITS) - 1;
// ========================== 线程池状态属性 ==============================
// 运行状态
private static final int RUNNING = -1 << COUNT_BITS; // 111
// 关闭状态,不接受新任务,继续处理工作队列中剩余任务
private static final int SHUTDOWN = 0 << COUNT_BITS; // 000
// 停止状态,不接受新任务,不处理工作队列中的任务
private static final int STOP = 1 << COUNT_BITS; // 001
// 过渡状态
private static final int TIDYING = 2 << COUNT_BITS; // 010
// 终止状态
private static final int TERMINATED = 3 << COUNT_BITS; // 011
// ========================== 线程池构造方法属性 ==============================
// 阻塞/工作队列
private final BlockingQueue<Runnable> workQueue;
// 线程池的线程工厂,用于创建线程
private volatile ThreadFactory threadFactory;
// 拒绝策略,用于在线程池已经关闭或者线程数已经达到最大值的情况下,对新任务的处理策略
private volatile RejectedExecutionHandler handler;
// 空闲线程最长存活时间
private volatile long keepAliveTime;
// 是否允许核心线程空闲退出,默认值为false
private volatile boolean allowCoreThreadTimeOut;
// 线程池的核心线程数
private volatile int corePoolSize;
// 线程池的最大线程数
private volatile int maximumPoolSize;
// ========================== 线程池数据统计属性 ==============================
// 线程池中历史最大线程数
private int largestPoolSize;
// 记录线程池已经处理完的任务数量(在每个工作线程退出时累加更新)
private long completedTaskCount;
构造方法
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
corePoolSize(核心线程数):当线程池运行的线程少于 corePoolSize 时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。
maximumPoolSize(最大线程数):线程池允许开启的最大线程数
keepAliveTime(保持存活时间):如果线程池当前线程数超过 corePoolSize,则多余的线程空闲时间超过 keepAliveTime 时会被终止。
TimeUnit:keepAliveTime 的时间单位
workQueue(工作队列):用于保留任务并移交给工作线程的阻塞队列
threadFactory(线程工厂):用于创建工作线程的工厂
handler(拒绝策略):线程池不能处理提交的任务时触发拒绝策略
内部类Worker:封装工作线程
// 中断线程不是立即让线程停止,只是将线程的中断标识设置为true
private final class Worker
extends AbstractQueuedSynchronizer // 线程中断 不同状态的线程池要针对不同状态的Worker进行中断
implements Runnable // 存储需要执行的任务
{
// 工作线程的Thread对象,初始化的时候构建出来
final Thread thread;
// 需要执行的任务
Runnable firstTask;
// 已完成的任务数
volatile long completedTasks;
Worker(Runnable firstTask) {
// 刚刚初始化的工作线程不允许被中断
setState(-1);
// 第一次new的时候,会将任务赋值给firstTask
this.firstTask = firstTask;
// 给Worker构建Thread对象
this.thread = getThreadFactory().newThread(this);
}
// Worker被调用start时,执行当前run方法
public void run() {
runWorker(this);
}
// state不等于0的两种情况:一种是等于1,另一种是等于-1
// 在Worker被创建时会 setState(-1),将state设置为-1
// 在runWorker方法中在 工作线程获取到任务开始执行前会w.lock()将state设置为1
// 当工作线程从工作队列中获取任务时被阻塞,此时 state = 0
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// 将state置为0,在runWorker中,为了表示当前线程允许被中断
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// 加锁,表明当前工作线程正在执行任务,在SHUTDOWN状态下,不允许被中断
// Worker内部实现的锁,是不可重入锁,在中断Worker时也需要进行lock,不能获取就代表当前工作线程正在执行任务,不可被中断
public void lock() { acquire(1); }
// 在调用shutdown方法时,会中断空闲的工作线程
// 在中断当前工作线程时,尝试加锁
// 若加锁失败,说明当前工作线程正在执行任务,不可被中断;若加锁成功,则中断当前工作线程
public boolean tryLock() { return tryAcquire(1); }
// 将state置为0,在runWorker中,为了表示当前线程允许被中断
public void unlock() { release(1); }
// Worker处于是否非阻塞状态
public boolean isLocked() { return isHeldExclusively(); }
// 在调用shutdownNow方法关闭线程池时,需要中断所有的工作线程
void interruptIfStarted() {
Thread t;
// getState()<0 的情况是在工作线程被创建出来的时候,这时的工作线程是不允许被中断的
// 在工作线程被启动调用 runWorker方法执行任务的时候,工作线程在执行任务前会检查线程池的状态
// 若线程池的状态为 STOP, 则会中断该工作线程
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
思考:Worker 为什么要继承 AbstractQueuedSynchronizer?
主要是为了解决线程中断问题,在空闲时可以响应中断,在执行任务时不可被中断。就是在中断之前首先尝试获得锁,trylock,由于不可重入特性,执行任务中的worker已经获得了lock,trylock失败,无法再中断woker对象的thread。
Worker执行任务时获得锁,执行完毕释放锁。Worker具有不可重入特性,目的是为了防止worker在线程池控制类操作时获得锁。
核心方法
execute:向线程池提交任务
public void execute(Runnable command) {
// 参数校验
if (command == null)
throw new NullPointerException();
// 获取ctl值
int c = ctl.get();
// 工作线程数小于核心线程数
if (workerCountOf(c) < corePoolSize) {
// 添加一个核心线程去执行command任务
if (addWorker(command, true))
// 添加核心线程成功
return;
// 如果在并发情况下,添加核心线程失败的线程,需要重新获取ctl值
c = ctl.get();
}
// 创建核心线程失败或工作线程数大于等于核心线程数,将任务添加到队列
// 线程池的状态是 RUNNING 状态,将任务添加到工作队列
if (isRunning(c) && workQueue.offer(command)) {
// 成功将任务添加到工作队列
// 再次重新检查ctl值
int recheck = ctl.get();
// 若线程池的状态不是RUNNING状态,则需要将刚加入到队列的任务从队列中移除
if (! isRunning(recheck) && remove(command))
// 线程池状态不正确,执行拒绝策略
reject(command);
// 线程池状态是RUNNING状态 或者 移除任务失败
// 判断工作线程是否为0,
else if (workerCountOf(recheck) == 0)
// 工作线程数为0,但是工作队列中有任务
// 若此时线程池的状态是SHUTDOWN状态,而线程池由SHUTDOWN状态变成TIDYING状态的前提是工作线程数为0且工作队列为空
// 所以添加一个空的非核心线程来处理在工作队列中排队的任务,避免该问题发生
addWorker(null, false);
}
// 线程池状态不是 RUNNING
// 或者线程池状态是 RUNNING,但是任务添加到工作队列失败
// 添加非核心线程去执行当前任务
else if (!addWorker(command, false))
// 添加非核心线程失败,执行拒绝策略
reject(command);
}
通过execute方法,可以看到线程池的整体执行流程,以及一些避免并发情况的判断
思考:为什么线程池会添加一个空任务的非核心线程到线程池?
主要是避免调用shutdown后 线程池没有工作线程且工作队列中还有任务 导致线程池不能由SHUTDOWN状态变成TIDYING状态。所以需要添加一个空的非核心线程来处理工作队列中的任务。
addWorker:添加工作线程
// 添加工作线程,并启动工作线程
private boolean addWorker(Runnable firstTask, boolean core) {
// 对线程池状态的判断,以及对工作线程数量的判断
// 外层for循环的标识
retry:
for (int c = ctl.get();;) { // 获取ctl的值
if (runStateAtLeast(c, SHUTDOWN) // 如果线程池状态不是RUNNING,就再次做后续判断
&& (runStateAtLeast(c, STOP) // 如果线程池状态是STOP或TIDYING或TERMINATED,这些状态都不会再处理新的任务
|| firstTask != null // 说明线程池状态是SHUTDOWN状态,允许创建空的非核心线程【对应execute方法中的addWorker(null, false)的情况】,若任务不为空,则不需要再添加新的工作线程
|| workQueue.isEmpty())) // 线程池状态是SHUTDOWN状态,firstTask又为null,若工作队列为空,不需要再添加新的工作线程
// 也就是说在线程池的状态不是 RUNNING 的情况下只有一种情况可以添加工作线程
// 那就是线程池状态是 SHUTDOWN,工作队列中还有任务时, 允许创建空任务非核心线程
return false;
// 内层for循环
for (;;) {
// 检查工作线程数是否合法,若不合法直接返回false
if (workerCountOf(c)
>= ((core ? corePoolSize : maximumPoolSize) & COUNT_MASK))
return false;
// 以cas方式,对工作线程数+1,如果成功,直接跳出外层循环
if (compareAndIncrementWorkerCount(c))
break retry;
// cas失败,有并发操作,重新获取ctl的值
c = ctl.get();
// 如果线程池状态不是RUNNING,需要跳到外层循环继续
if (runStateAtLeast(c, SHUTDOWN))
continue retry;
// 若线程池状态是RUNNING,cas失败后,只需在内层循环判断
}
}
// 尝试添加工作线程,并启动工作线程
boolean workerStarted = false; // 工作线程是否启动
boolean workerAdded = false; // 工作线程是否添加
Worker w = null; // 工作线程
try {
// 构建工作线程,将任务扔到了Worker中
w = new Worker(firstTask);
// 拿到了Worker中绑定的线程(在上面的new Worker()中创建了线程)
final Thread t = w.thread;
// 肯定不为null (健壮性判断)
if (t != null) {
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 重新获取ctl的值
int c = ctl.get();
if (isRunning(c) || // 如果线程池状态是RUNNING,就添加工作线程
(runStateLessThan(c, STOP) && firstTask == null)) { // 如果线程池状态是SHUTDOWN,并且传入的任务是null【对应execute方法中的addWorker(null, false)的情况】
// 健壮性判断:如果线程已经被start 抛出异常
if (t.getState() != Thread.State.NEW)
throw new IllegalThreadStateException();
// 将构建好的Worker将入到workers
workers.add(w);
// 将工作线程添加的标识设置为true
workerAdded = true;
// 获取工作线程数量
int s = workers.size();
// 如果现在的工作线程数大于历史最大的工作线程数,就重新赋值历史最大的工作线程数largestPoolSize
if (s > largestPoolSize)
largestPoolSize = s;
}
} finally {
// 释放锁
mainLock.unlock();
}
if (workerAdded) {
// 添加工作线程成功,启动工作线程
t.start();
// 将工作线程启动的标识设置为true
workerStarted = true;
}
}
} finally {
// 如果启动工作线程失败
if (! workerStarted)
// 移除workers里的工作线程,将工作线程数-1,尝试修改线程池状态为 TIDYING
addWorkerFailed(w);
}
// 工作线程已启动返回true, 工作线程未启动返回fasle
return workerStarted;
}
// 启动工作线程失败,做的补救工作
private void addWorkerFailed(Worker w) {
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 如果工作线程不为null, 则将工作线程从集合中移除
if (w != null)
workers.remove(w);
// 将工作线程数减1
decrementWorkerCount();
// 尝试将线程池状态变为 TIDYING
tryTerminate();
} finally {
// 释放锁
mainLock.unlock();
}
}
runWorker: 工作线程执行工作任务
// 执行任务的流程,并且做了中断相关的lock操作
final void runWorker(Worker w) {
// 拿到当前工作线程
Thread wt = Thread.currentThread();
// 拿到Worker中封装的任务
Runnable task = w.firstTask;
w.firstTask = null;
// 将Worker的state置为0,代表可以被中断
w.unlock();
// 标识任务执行期间有没有出异常, 若任务正常执行完,则completedAbruptly会被设置为false
boolean completedAbruptly = true;
try {
// 获取任务的第一个方式,就是执行execute、submit时,传入的任务任务直接处理
// 获取任务的第二个方式,就是从工作队列中获取任务执行。
while (task != null || (task = getTask()) != null) {
// 加锁,在SHUTDOWN状态下,当前线程不允许被中断
// 并且Worker内部实现的锁,并不是可重入锁,因为在中断时,也需要对Worker进行lock,不能获取就代表当前工作线程正在执行任务
w.lock();
// 如果线程池状态变成了STOP状态,必须将当前线程中断
if ((runStateAtLeast(ctl.get(), STOP) ||(Thread.interrupted() && runStateAtLeast(ctl.get(), STOP)))
&& !wt.isInterrupted()) // 当前工作线程的中断标识为false
// 将工作线程的中断标识设置为true
wt.interrupt();
try {
// 执行任务开始前的钩子函数
beforeExecute(wt, task);
try {
task.run();
// 执行任务结束后的钩子函数(正常结束)
afterExecute(task, null);
} catch (Throwable ex) {
// 执行任务结束后的钩子函数(异常结束)
afterExecute(task, ex);
throw ex;
}
} finally {
task = null;
// 已执行的任务个数+1
w.completedTasks++;
// 将state标记设置为0,任务执行完毕,当前线程可以被中断
w.unlock();
}
}
// 如果任务正常执行完,completedAbruptly为false
completedAbruptly = false;
} finally {
processWorkerExit(w, completedAbruptly);
}
}
getTask:从工作队列中获取任务
// 从工作队列中获取任务
private Runnable getTask() {
// 标识非核心线程是否可以被干掉
boolean timedOut = false;
// 死循环
for (;;) {
// ========================== 判断线程池的状态 ================================
int c = ctl.get();
// 如果线程池状态为大于等于STOP状态,此状态下线程池不再执行任务,需要销毁当前工作线程
// 如果线程池状态为SHUTDOWN,并且工作队列为空,需要销毁当前工作线程
if (runStateAtLeast(c, SHUTDOWN)
&& (runStateAtLeast(c, STOP) || workQueue.isEmpty())) {
// 将工作线程数减1
decrementWorkerCount();
// 返回null 交给processWorkerExit方法将工作线程从workers中移除
return null;
}
// ========================== 判断工作线程的数量 ================================
// 计算工作线程数量
int wc = workerCountOf(c);
// 允许核心线程超时或者工作线程数大于核心线程数
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
if (
// 工作线程数是否大于最大线程数(不可能发生)
// 工作线程数大于核心线程数,并且当前线程已经超时
// 尝试销毁当前线程
(wc > maximumPoolSize || (timed && timedOut))
// 如果工作线程大于1 或者 工作队列为空 就销毁当前线程
&& (wc > 1 || workQueue.isEmpty())) {
// 用cas将当前工作线程数减1
if (compareAndDecrementWorkerCount(c))
// 返回null 交给processWorkerExit方法将工作线程从workers中移除
return null;
// 存在并发修改,失败的线程continue
continue;
}
// 线程池状态和工作线程数都符合要求,从工作队列中获取任务
try {
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : // 阻塞一定时间从工作队列中拿任务(可以理解为非核心线程走这个方法)
workQueue.take(); // 一直阻塞(可以理解为核心线程走这个方法)
if (r != null)
// 拿到任务直接返回执行
return r;
// 从队列获取任务超时(达到了当前工作线程的最大生存时间)
// 设置超时标识为true
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false;
}
}
}
processWorkerExit:工作线程退出
工作线程在执行任务时退出,主要分为两种:一种是工作线程在执行任务时出现异常,异常退出;另一种是工作线程从工作队列中获取不到任务,正常退出
// 在工作线程执行任务时出异常 或者是 工作线程在执行getTask方法时返回null的情况下会执行该方法, 移除当前工作线程
private void processWorkerExit(Worker w, boolean completedAbruptly) {
// 工作线程执行任务时出异常进入该方法
if (completedAbruptly)
// 工作线程数减1 (工作线程在获取任务时返回null进入该方法时,将工作线程数减1的操作在getTask方法中已经执行)
decrementWorkerCount();
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 记录当前线程池一共处理了多少个任务
completedTaskCount += w.completedTasks;
// 移除工作线程
workers.remove(w);
} finally {
// 解锁
mainLock.unlock();
}
// 尝试将线程池关闭 (到TIDYING状态 - 再到TERMINATED状态)
tryTerminate();
// 重新获取ctl的值
int c = ctl.get();
// 线程池的状态是 RUNNING 或者是 SHUTDOWN
if (runStateLessThan(c, STOP)) {
// 工作线程在执行getTask方法时返回null的情况下进入该方法
if (!completedAbruptly) {
// 计算允许的核心线程数
int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
// 若允许的核心线程数为0,并且工作队列中还有任务,这时需要保证的核心线程数最小为1
if (min == 0 && ! workQueue.isEmpty())
min = 1;
// 线程池中还有工作线程 直接return
if (workerCountOf(c) >= min)
return;
}
// 在工作线程执行任务出异常时异常了当前线程,需要添加一个工作线程
// 线程池中没有了工作线程并且工作队列还有任务,需要再添加一个工作线程
addWorker(null, false);
}
}
tryTerminate:尝试终止线程池
// addWorkerFailed方法、
// processWorkerExit方法、
// shutdown方法、
// shutdownNow方法
// remove方法
// purge方法
// 尝试终止线程池
final void tryTerminate() {
// 自旋
for (;;) {
// 拿到ctl
int c = ctl.get();
// 若线程池状态为 RUNNING, return
// 若线程池的状态为 TIDYING 或 TERMINATED, return
// 若线程状态为 SHUTDOWN 且工作队列中还有任务, return
if (isRunning(c) ||
runStateAtLeast(c, TIDYING) ||
(runStateLessThan(c, STOP) && ! workQueue.isEmpty()))
return;
// 能执行到这里,说明两种情况:
// 1. 线程池的状态为 SHUTDOWN 且工作队列中没有任务
// 2. 线程池的状态为 STOP
// 以上两种情况都可以终止线程池
// 线程池中还有工作线程,线程池不能被终止
if (workerCountOf(c) != 0) {
// ONLY_ONE 默认值为 true
// 遍历一个工作线程,尝试中断该工作线程
interruptIdleWorkers(ONLY_ONE);
return;
}
// 能执行到这里 说明线程池中没有了工作线程
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 尝试用cas的方式 将线程池的状态设置为 TIDYING 且工作线程数量为0
if (ctl.compareAndSet(c, ctlOf(TIDYING, 0))) {
try {
// 线程池终止前的钩子函数
terminated();
} finally {
// 将线程池的状态设置为 TERMINATED
ctl.set(ctlOf(TERMINATED, 0));
// 唤醒调用 awaitTermination() 方法的线程
termination.signalAll();
}
return;
}
} finally {
mainLock.unlock();
}
// 若cas失败 存在并发冲突 自旋重试
}
}
awaitTermination:超时等待线程池终止
public boolean awaitTermination(long timeout, TimeUnit unit)
throws InterruptedException {
// 将超时时间转换为纳秒
long nanos = unit.toNanos(timeout);
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 线程池状态不是 TERMINATED
while (runStateLessThan(ctl.get(), TERMINATED)) {
// 若超时时间到已到 返回false
if (nanos <= 0L)
return false;
// 等待线程池终止
nanos = termination.awaitNanos(nanos);
}
// 线程池状态是 TERMINATED 返回true
return true;
} finally {
// 解锁
mainLock.unlock();
}
}
shutdown:优雅关闭线程池
关闭线程池,不接受新的任务,仍会处理工作队列中的任务
public void shutdown() {
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 将线程池的状态设置为 SHUTDOWN
advanceRunState(SHUTDOWN);
// 中断所有空闲的工作线程
interruptIdleWorkers();
// 钩子函数
onShutdown(); // hook for ScheduledThreadPoolExecutor
} finally {
mainLock.unlock();
}
// 尝试终止线程池
tryTerminate();
}
// 将线程池的状态设置为 SHUTDOWN
private void advanceRunState(int targetState) {
for (;;) {
int c = ctl.get();
if (runStateAtLeast(c, targetState) || // 若线程池状态已经是目标状态 break
ctl.compareAndSet(c, ctlOf(targetState, workerCountOf(c)))) // 用cas的方式将线程池状态设置为 目标状态 设置成功则break
break;
}
}
// 中断所有空闲的工作线程
private void interruptIdleWorkers() {
interruptIdleWorkers(false);
}
interruptIdleWorkers:中断空闲的工作线程
// 中断空闲的工作线程
// onlyOne 为true, 只遍历一个工作线程; 若为false 则遍历所有的工作线程
private void interruptIdleWorkers(boolean onlyOne) {
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// 遍历所有工作线程
for (Worker w : workers) {
Thread t = w.thread;
// 线程的中断标识为fasle 并且该工作线程没有在执行任务 调用interrupt方法 将线程的中断标识设置为true
if (!t.isInterrupted()
&& w.tryLock()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
} finally {
w.unlock();
}
}
// 若为true 说明只遍历一个工作线程 跳出循环
if (onlyOne)
break;
}
} finally {
// 解锁
mainLock.unlock();
}
}
思考:如何知道当前工作线程是否空闲?
首先 Worker 是内部自己实现的锁,是不可重入锁,相当于内部有个 state 属性值,在 Worker 执行任务前会 w.lock 将 state 设置为1,表明自己正在执行任务,也就是处于非空闲状态。
shutdownNow:立刻关闭线程池
尝试中断所有的工作线程,并且不再处理工作队列中的任务,并返回工作队列中的所有任务。
public List<Runnable> shutdownNow() {
List<Runnable> tasks;
// 加锁
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
checkShutdownAccess();
// 将线程池的状态设置为 SHUTDOWN
advanceRunState(STOP);
// 中断所有的工作线程
interruptWorkers();
// 返回工作队列中的所有任务
tasks = drainQueue();
} finally {
mainLock.unlock();
}
// 尝试终止线程池
tryTerminate();
return tasks;
}
// 中断所有的工作线程
private void interruptWorkers() {
// 遍历所有工作线程,逐个中断
for (Worker w : workers)
w.interruptIfStarted();
}
// 中断已经started了的工作线程
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
// 移除队列中的任务,并返回所有任务
private List<Runnable> drainQueue() {
BlockingQueue<Runnable> q = workQueue;
ArrayList<Runnable> taskList = new ArrayList<>();
q.drainTo(taskList);
// 若drainTo后工作队列仍有任务 则需要手动移除
if (!q.isEmpty()) {
for (Runnable r : q.toArray(new Runnable[0])) {
if (q.remove(r))
taskList.add(r);
}
}
return taskList;
}
线程池的状态变换
常见线程池
SingleThreadExecutor
单线程的线程池,核心线程数和最大线程数都为1,无界阻塞队列。适用于需要保证任务按顺序执行的场景。
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
FixedThreadPool
固定大小的线程池,线程数量固定,适用于负载较重的服务器。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
CachedThreadPool
可缓存的线程池,线程数量不固定,适用于执行时间较短的任务。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ScheduledThreadPoolExecutor
定时任务线程池,用于执行定时任务和周期性任务。 ScheduledThreadPoolExecutor 继承自 ThreadPoolExecutor
public ScheduledThreadPoolExecutor(int corePoolSize,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
// DEFAULT_KEEPALIVE_MILLIS 默认为10
super(corePoolSize, Integer.MAX_VALUE,
DEFAULT_KEEPALIVE_MILLIS, MILLISECONDS,
new DelayedWorkQueue(), threadFactory, handler);
}
阻塞队列
Queue接口
入队、出队和检索均有两种实现,区别在于其中一种会在方法执行失败时抛出异常,而另一种会在失败时返回值。
public interface Queue<E> extends Collection<E> {
// ==================== 入队方法 ===================
// 添加一个元素,添加成功返回true, 如果添加失败,就会抛出异常
boolean add(E e);
// 添加一个元素,添加成功返回true, 如果添加失败,返回false
boolean offer(E e);
// ==================== 出队方法 ===================
// 返回并删除队首元素,队列为空则抛出异常
E remove();
// 返回并删除队首元素,队列为空则返回null
E poll();
// ==================== 查看队首元素方法 =============
// 查看队首元素,但不移除,队列为空则抛出异常
E element();
// 查看队首元素,但不移除,队列为空则返回null
E peek();
}
BlockingQueue
BlockingQueue 继承了 Queue 接口,是队列的一种。Queue 和 BlockingQueue 都是在 Java 5 中加入的。BlockingQueue 是一个在队列基础上又支持了两个附加操作的队列,常用解耦。两个附加操作:
- 支持阻塞的插入方法put:队列满时,队列会阻塞插入元素的线程,直到队列不满。
- 支持阻塞的移除方法take:队列空时,获取元素的线程会等待队列变为非空
public interface BlockingQueue<E> extends Queue<E> {
// ======================================== 入队方法 =============================================
// 入队,入队成功返回true,如果入队失败,就会抛出异常
boolean add(E e);
// 入队,入队成功返回true,如果入队失败,返回false
boolean offer(E e);
// 入队,队列没满的时候是正常的入队,如果队列已满,则阻塞,直至队列空出位置 (响应线程中断)
void put(E e) throws InterruptedException;
// 入队,入队成功返回true,如果队列已满,则进行超时阻塞,若超时,则返回false (响应线程中断)
boolean offer(E e, long timeout, TimeUnit unit) throws InterruptedException;
// ======================================== 出队方法 =============================================
// 出队,队列里有数据则正常出队,并删除数据;如果队列为空,则阻塞,直到队列里有数据 (响应线程中断)
E take() throws InterruptedException;
// 出队,队列里有数据则正常出队,并删除数据;如果队列为空,则超时阻塞,若超时,则返回null (响应线程中断)
E poll(long timeout, TimeUnit unit) throws InterruptedException;
// ======================================== 其他方法 =============================================
// 队列中剩余的容量
int remainingCapacity();
// 从队列中移除指定元素
boolean remove(Object o);
// 队列中是否包含指定元素
boolean contains(Object o);
// 将队列中的所有元素删除并转移到指定集合,返回转移元素的数量
int drainTo(Collection<? super E> c);
// 从队列中删除指定数量的元素并转移到指定集合,返回转移元素的数量
int drainTo(Collection<? super E> c, int maxElements);
}
常见的阻塞队列
BlockingQueue 接口的实现类都被放在了 juc 包中,它们的区别主要体现在存储结构上或对元素操作上的不同,但是对于take与put操作的原理,却是类似的。
ArrayBlockingQueue
ArrayBlockingQueue是最典型的有界阻塞队列,其内部是用数组存储元素的,初始化时需要指定容量大小,利用ReentrantLock
实现线程安全。在生产者-消费者模型中使用时,如果生产速度和消费速度基本匹配的情况下,使用 ArrayBlockingQueue是个不错选择;当如果生产速度远远大于消费速度,则会导致队列填满, 大量生产线程被阻塞。使用独占锁ReentrantLock实现线程安全,入队和出队操作使用同一个锁对象,也就是只能 有一个线程可以进行入队或者出队操作;这也就意味着生产者和消费者无法并行操作,在高并发 场景下会成为性能瓶颈。
思考:为什么对数组操作要设计成双指针?
数组的删除的时间复杂度为O(n),因为数组删除,删除数据后面的下标要往前移动。如果设计成环形数组,移动的只是指针,时间复杂度为O(1)。
LinkedBlockingQueue
LinkedBlockingQueue是一个基于链表实现的阻塞队列,默认情况下,该阻塞队列的大小为Integer.MAX_VALUE,由于这个数值特别大,所以 LinkedBlockingQueue 也被称作无界队列,代表它几乎没有界限,队列可以随着元素的添加而动态增长,但是如果没有剩余内存,则队列将抛出OOM错误。所以为了避免队列过大造成机器负载或者内存爆满的情况出现,我们在使用的时候建议手动传一个队列的大小。
LinkedBlockingQueue内部由单链表实现,只能从head取元素,从tail添加元素。LinkedBlockingQueue采用两把锁的锁分离技术实现入队出队互不阻塞,添加元素和获取元素都有独立的锁,也就是说LinkedBlockingQueue是读写分离的,读写操作可以并行执行
思考:线程池为什么使用LinkedBlockingQueue而不是用ArrayBlockingQueue?
因为LinkedBlockingQueue入队和出队是两把锁,存取元素互不干扰;而ArrayBlockingQueue使用的是同一把锁,存取元素互相排斥。所以LinkedBlockingQueue性能更好一些。
LinkedBlockingQueue与ArrayBlockingQueue对比
LinkedBlockingQueue是一个阻塞队列,内部由两个ReentrantLock来实现出入队列的线程安全,由各自的Condition对象的await和signal来实现等待和唤醒功能。它和ArrayBlockingQueue的不同点在于:
- 队列大小有所不同,ArrayBlockingQueue是有界的初始化必须指定大小,而LinkedBlockingQueue可以是有界的也可以是无界的(Integer.MAX_VALUE),对于后者而言,当添加速度大于移除速度时,在无界的情况下,可能会造成内存溢出等问题。
- 数据存储容器不同,ArrayBlockingQueue采用的是数组作为数据存储容器,而LinkedBlockingQueue采用的则是以Node节点作为连接对象的链表。
由于ArrayBlockingQueue采用的是数组的存储容器,因此在插入或删除元素时不会产生或销毁任何额外的对象实例,而LinkedBlockingQueue则会生成一个额外的Node对象。这可能在长时间内需要高效并发地处理大批量数据的时,对于GC可能存在较大影响。 - 两者的实现队列添加或移除的锁不一样,ArrayBlockingQueue实现的队列中的锁是没有分离的,即添加操作和移除操作采用的同一个ReenterLock锁,而LinkedBlockingQueue实现的队列中的锁是分离的,其添加采用的是putLock,移除采用的则是takeLock,这样能大大提高队列的吞吐量,也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
LinkedBlockingDeque
LinkedBlockingDeque是一个双端链表,在单一方向上和 LinkedBlockingQueue 大体相同。
LinkedBlockingDeque和LinkedBlockingQueue一样指定容量是有界队列,不指定容量是无界队列。所用的数据结构也是链表,不过用的锁和ArrayBlockingQueue一样,采用同一把锁。
SynchronousQueue
SynchronousQueue是一个没有数据缓冲的BlockingQueue,生产者线程对其的插入操作 put必须等待消费者的移除操作take。
SynchronousQueue 最大的不同之处在于,它的容量为 0,所以没有一个地方来暂存元素,导致每次取数据都要先阻塞,直到有数据被放入;同理,每次放数据的时候也会阻塞,直到有消费者来取。 需要注意的是,SynchronousQueue 的容量不是 1 而是 0,因为 SynchronousQueue 不需 要去持有元素,它所做的就是直接传递(direct handoff)。由于每当需要传递的时候, SynchronousQueue 会把元素直接从生产者传给消费者,在此期间并不需要做存储,所以如果运用得当,它的效率是很高的。
LinkedTransferQueue
PriorityBlockingQueue
PriorityBlockingQueue 是一个无界的基于数组的优先级阻塞队列,数组的默认长度是11,虽然指定了数组的长度,但是可以无限的扩充,直到资源消耗尽为止,每次出队都返回优先级别最高的或者最低的元素。默认情况下元素采用自然顺序升序排序,当然我们也可以通过构造函数来指定Comparator来对元素进行排序。需要注意的是PriorityBlockingQueue不能保证同优先级元素的顺序。
DelayQueue
DelayQueue 是一个支持延时获取元素的阻塞队列, 内部采用优先队列 PriorityQueue 存储元素,同时元素必须实现 Delayed 接口;在创建元素时可以指定多久才可以从队列中获取当前元素,只有在延迟期满时才能从队列中提取元素。延迟队列的特点是:不是先进先出,而是会按照延迟时间的长短来排序,下一个即将执行的任务会排到队列的最前面。
它是无界队列,放入的元素必须实现 Delayed 接口,而 Delayed 接口又继承了 Comparable 接口,所以自然就拥有了比较和排序的能力。
如何选择适合的阻塞队列
线程池有很多种,不同种类的线程池会根据自己的特点,来选择适合自己的阻塞队列。
- FixedThreadPool 和 SingleThreadExecutor 选取的是 LinkedBlockingQueue
- CachedThreadPool 选取的是 SynchronousQueue
- ScheduledThreadPool 和 SingleThreadScheduledExecutor 选取的是DelayedWorkQueue(一种专用的延时队列)
选择策略
通常我们可以从以下 5 个角度考虑,来选择合适的阻塞队列:
- 功能
第 1 个需要考虑的就是功能层面,比如是否需要阻塞队列帮我们排序,如优先级排序、延迟执行等。如果有这个需要,我们就必须选择类似于 PriorityBlockingQueue 之类的有排序能力的阻塞队列。 - 容量
第 2 个需要考虑的是容量,或者说是否有存储的要求,还是只需要“直接传递”。在考虑这一点的时候,我们知道前面介绍的那几种阻塞队列,有的是容量固定的,如 ArrayBlockingQueue;有的默认是容量无限的,如 LinkedBlockingQueue;而有的里面没有任何容量,如 SynchronousQueue;而对于 DelayQueue 而言,它的容量固定就是 Integer.MAX_VALUE。所以不同阻塞队列的容量是千差万别的,我们需要根据任务数量来推算出合适的容量,从而去选取合适的 BlockingQueue。 - 能否扩容
第 3 个需要考虑的是能否扩容。因为有时我们并不能在初始的时候很好的准确估计队列的大小,因为业务可能有高峰期、低谷期。如果一开始就固定一个容量,可能无法应对所有的情况,也是不合适的,有可能需要动态扩容。如果我们需要动态扩容的话,那么就不能选择 ArrayBlockingQueue ,因为它的容量在创建时就确定了,无法扩容。相反,PriorityBlockingQueue 即使在指定了初始容量之后,后续如果有需要,也可以自动扩容。所以我们可以根据是否需要扩容来选取合适的队列。 - 内存结构
第 4 个需要考虑的点就是内存结构。 ArrayBlockingQueue 的内部结构是“数组”的形式。LinkedBlockingQueue 的内部是用链表实现的,所以这里就需要我们考虑到,ArrayBlockingQueue 没有链表所需要的“节点”,空间利用率更高。所以如果我们对性能有要求可以从内存的结构角度去考虑这个问题。 - 性能
第 5 点就是从性能的角度去考虑。比如 LinkedBlockingQueue 由于拥有两把锁,它的操作粒度更细,在并发程度高的时候,相对于只有一把锁的 ArrayBlockingQueue 性能会更好。另外,SynchronousQueue 性能往往优于其他实现,因为它只需要“直接传递”,而不需要存储的过程。如果我们的场景需要直接传递的话,可以优先考虑 SynchronousQueue。
拒绝策略
根据excute方法的源码可知,一共有3种情况会执行拒绝策略:
- 线程池的状态不是 RUNNING,会对提交的任务执行拒绝策略;
- 线程池的状态是 RUNNING,线程池中核心线程数满了,则需要将任务放入工作队列,在将任务成功放入队列后,线程池的状态又变成不是 RUNNING,这时需要将任务对工作队列中移除,若成功移除,则对该任务执行拒绝策略;
- 线程池的状态是 RUNNING,线程池中的工作线程数已经等于允许的最大线程池数且工作队列也满了,这时如果还有任务过来,线程池就会执行拒绝策略。
ThreadPoolExecutor 内置了四种拒绝策略,分别是CallerRunsPolicy、AbortPolicy、DiscardPolicy、DiscardOldestPolicy。当然也可以通过实现 RejectedExecutionHandler 接口来自定义拒绝策略。
CallerRunsPolicy
调用者运行策略。若线程池的状态不是 RUNNING 状态,则什么也不做;如果线程池的状态是 RUNNING 状态,在调用者线程中执行该任务。
该策略实现了一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将任务回退到调用者(调用线程池执行任务的主线程),由于执行任务需要一定时间,因此主线程至少在一段时间内不能提交任务,从而使得线程池有时间来处理完正在执行的任务。
public static class CallerRunsPolicy implements RejectedExecutionHandler {
public CallerRunsPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 线程池状态是 RUNNING
if (!e.isShutdown()) {
r.run();
}
}
}
AbortPolicy
中止策略,默认的拒绝策略,直接抛 RejectedExecutionException 异常。调用者可以捕获这个异常,然后根据需求编写自己的处理代码。
public static class AbortPolicy implements RejectedExecutionHandler {
public AbortPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
DiscardPolicy
抛弃策略。什么都不做,直接抛弃被拒绝的任务。
public static class DiscardPolicy implements RejectedExecutionHandler {
public DiscardPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
DiscardOldestPolicy
抛弃最老策略。若线程池的状态不是 RUNNING 状态,则什么也不做;如果线程池的状态是 RUNNING 状态,从工作队列中抛弃一个任务后,再交由线程池处理。
抛弃工作队列中最老的任务,相当于就是队列中下一个将要被执行的任务,然后重新提交被拒绝的任务。如果阻塞队列是一个优先队列,那么“抛弃最旧的”策略将导致抛弃优先级最高的任务,因此最好不要将该策略和优先级队列放在一起使用。
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
public DiscardOldestPolicy() { }
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// 线程池状态是 RUNNING
if (!e.isShutdown()) {
// 抛弃一个任务
e.getQueue().poll();
e.execute(r);
}
}
}