一、提供了什么功能?
一个
ExecutorService
接口的实现类,使用多个线程池中的一个来执行每个提交的任务,通常通过Executors
工厂方法进行配置。
线程池解决了两个不同的问题:
- 在执行大量异步任务时提供更好的性能,因为减少了每个任务调用的开销,并且它们提供了一种方式来限制和管理在执行一组任务时消耗的资源(包括线程)。
- 每个
ThreadPoolExecutor
还维护一些基本统计信息,例如已完成任务的数量。
为了在广泛的上下文中有用,该类提供了许多可调参数和扩展钩子。
然而,建议程序员使用更方便的Executors
工厂方法,例如:
Executors.newCachedThreadPool
(无界线程池,具有自动线程回收)Executors.newFixedThreadPool
(固定大小线程池)Executors.newSingleThreadExecutor
(单个后台线程)
这些方法为最常见的使用场景预配置了设置。
构造函数
/**
* 使用给定的初始参数和默认线程工厂及拒绝执行处理程序创建一个新的ThreadPoolExecutor。
*
* 参数:
* - corePoolSize – 即使线程处于空闲状态,也要保持在池中的线程数量,除非设置了allowCoreThreadTimeOut。
* - maximumPoolSize – 允许在池中存在的最大线程数量。
* - keepAliveTime – 当线程数量超过核心线程数时,多余的空闲线程在终止之前等待新任务的最大时间。
* - unit – keepAliveTime参数的时间单位。
* - workQueue – 用于在任务执行之前保存任务的队列。此队列将仅保存通过execute方法提交的Runnable任务。
*
* 抛出:
* - IllegalArgumentException – 如果以下任一条件成立:
* - corePoolSize < 0
* - keepAliveTime < 0
* - maximumPoolSize <= 0
* - maximumPoolSize < corePoolSize
* - NullPointerException – 如果workQueue为null
*/
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
// 指定拒绝策略处理器的重载方法
// 若handler的值为null,抛出NullPointerException异常
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), handler);
}
// 指定线程工厂的重载方法
// 若threadFactory的值为null,抛出NullPointerException异常
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
threadFactory, defaultHandler);
}
// 指定线程工厂和拒绝策略处理器的重载方法
// 若threadFactory或handler的值为null,抛出NullPointerException异常
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.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
execute()
private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
private static int ctlOf(int rs, int wc) { return rs | wc; }
private static int workerCountOf(int c) { return c & CAPACITY; }
/**
* 在未来的某个时间执行给定的任务。任务可以在新线程中执行,也可以在现有的池线程中执行。如果任务无法提交执行,可能是因为该执行器已关闭或其容量已达到,则任务将由当前的RejectedExecutionHandler处理。
*
* 参数:
* - command – 要执行的任务
*
* 抛出:
* - RejectedExecutionException – 如果任务无法被接受执行,具体取决于RejectedExecutionHandler的判断
* - NullPointerException – 如果command为null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* 按照以下三个步骤进行:
*
* 1. 如果运行中的线程少于 corePoolSize,尝试启动一个新线程,并将给定的任务作为其首个任务。
* 调用 addWorker 方法会原子地检查 runState 和 workerCount,以防止在不应该添加线程时误报(通过返回 false)。
*
* 2. 如果任务能够成功入队,我们还需要再次检查是否应该添加线程(因为上次检查后可能有线程已终止)
* 或者线程池自进入此方法以来已经关闭。因此,我们需要重新检查状态,如果已停止,则需要回滚入队操作;
* 如果没有线程,则启动一个新的线程。
*
* 3. 如果无法将任务入队,则尝试添加一个新的线程。如果失败,我们知道线程池已关闭或已饱和,
* 因此拒绝该任务。
*/
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}
addWorker()
/**
* 根据当前线程池的状态和给定的限制(核心线程数或最大线程数),检查是否可以添加新的工作线程。
* 如果可以,相应地调整工作线程的数量,并尽可能创建并启动一个新的工作线程,使其首先执行 firstTask 作为其首个任务。
* 如果线程池已停止或符合关闭条件,则此方法返回 false。
* 如果线程工厂在被要求创建线程时未能成功创建线程(通常是因为线程工厂返回 null 或抛出异常,例如在 Thread.start() 中出现 OutOfMemoryError),我们也会干净地回滚。
*
* 参数: - firstTask:新线程应首先运行的任务(或者为 null,如果没有)。
* 当线程数量少于 corePoolSize 时,我们总是启动一个新线程,并为其分配一个初始任务(在 execute() 方法中实现),以避免排队;
* 或者当队列已满时,我们也必须绕过队列。最初处于空闲状态的线程通常是通过 prestartCoreThread 创建的,或是用于替换其他死亡的工作线程。
* core:如果为 true,则使用 corePoolSize 作为限制;否则使用 maximumPoolSize。
*
* 返回值: - 如果成功,则返回 true。
*/
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;
}
二、手动配置指南:
核心和最大池大小
ThreadPoolExecutor
将根据corePoolSize
和maximumPoolSize
设置的边界自动调整池大小。
- 当在
execute(Runnable)
方法中提交新任务,并且运行的线程少于corePoolSize
时,将创建一个新线程来处理请求,即使其他工作线程处于空闲状态。 - 如果运行的线程数超过
corePoolSize
但少于maximumPoolSize
,则仅在队列满时才会创建新线程。
借用guide哥的图片展示如下:
通过将corePoolSize
和maximumPoolSize
设置为相同,可以创建一个固定大小的线程池。(以Executors类提供的方法为例)
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
通过将maximumPoolSize
设置为基本上无界的值(例如Integer.MAX_VALUE),可以使池容纳任意数量的并发任务。(以Executors类提供的方法为例)
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
通常情况下,core和maximum池大小仅在构造时设置,但也可以使用setCorePoolSize
和setMaximumPoolSize
动态更改。
若是需要精细化的调整core和maximum配置的话,还是直接使用ThreadPoolExecutor
类创建线程池好。
按需构造
默认情况下,即使核心线程在新任务到达时才会被创建和启动,但可以使用方法prestartCoreThread
或prestartAllCoreThreads
动态覆盖此行为。
如果您使用非空队列构造池,您可能希望预启动线程。
创建新线程
新线程是通过ThreadFactory
创建的。
如果没有其他指定,使用Executors.defaultThreadFactory
,该工厂创建的线程都在同一个ThreadGroup
中,并具有相同的NORM_PRIORITY
优先级和非守护状态。
通过提供不同的ThreadFactory
,您可以更改线程的名称、线程组、优先级、守护状态等。
如果ThreadFactory
在请求时返回null
以表示无法创建线程,执行器将继续运行,但可能无法执行任何任务。
线程应具有“modifyThread”
运行时权限。
如果工作线程或使用池的其他线程没有此权限,服务可能会降低:配置更改可能无法及时生效,关闭的池可能保持在可能终止但未完成的状态。
保持活动时间
如果池当前有超过corePoolSize
的线程,超过keepAliveTime
空闲的多余线程将被终止。
这提供了一种在池未被积极使用时减少资源消耗的手段。如果池在稍后变得更活跃,将构造新线程。
通过使用setKeepAliveTime(long, TimeUnit)
方法可以动态更改此参数。
使用Long.MAX_VALUE TimeUnit.NANOSECONDS
的值有效地禁用空闲线程在关闭之前的终止。
默认情况下,保持活动策略仅在超过corePoolSize
的线程时应用。但方法allowCoreThreadTimeOut(boolean)
可以用于将此超时策略应用于核心线程,只要keepAliveTime
值非零。
排队
可以使用任何BlockingQueue
来传输和保存提交的任务。此队列的使用与池大小相互作用:
- 如果运行的线程少于
corePoolSize
,Executor始终优先添加新线程而不是排队。 - 如果运行的线程数等于或超过
corePoolSize
,Executor始终优先排队请求而不是添加新线程。 - 如果请求无法排队,则会创建新线程,除非这会超过
maximumPoolSize
,在这种情况下,任务将被拒绝。
有三种一般的排队策略:
-
直接交接。对于工作队列,
SynchronousQueue
是一个不错的默认选择,它将任务交给线程,而不持有它们。在这里,如果没有线程立即可用来运行任务,则尝试排队任务将失败,因此将构造一个新线程。这种策略在处理可能具有内部依赖关系的请求集时避免了锁定。直接交接通常需要无界的maximumPoolSize
以避免拒绝新提交的任务。这反过来又允许在命令继续以平均速度到达时,线程数量无限增长。 -
无界队列。使用无界队列(例如
没有预定义容量的LinkedBlockingQueue
)将在所有corePoolSize
线程忙时使新任务在队列中等待。因此,最多只会创建corePoolSize
个线程。(因此,maximumPoolSize
的值没有任何效果。)当每个任务完全独立于其他任务时,这可能是合适的,因为任务不能影响彼此的执行;例如,在网页服务器中。虽然这种排队风格在平滑瞬时请求激增时非常有用,但当命令继续以平均速度到达时,它允许工作队列无限增长。 -
有界队列。有界队列(例如
ArrayBlockingQueue
)在与有限的maximumPoolSize
一起使用时有助于防止资源耗尽,但可能更难以调整和控制。队列大小和最大池大小可以互相权衡:使用大队列和小池可以最小化CPU使用、操作系统资源和上下文切换开销,但可能导致人为地低吞吐量。如果任务经常阻塞(例如,如果它们是I/O密集型的),系统可能能够调度比您允许的更多线程。使用小队列通常需要较大的池大小,这会使CPU更忙,但可能会遇到不可接受的调度开销,这也会降低吞吐量。
被拒绝的任务
在execute(Runnable)
方法中提交的新任务将在Executor
已关闭时被拒绝,并且在Executor
使用有限的最大线程和工作队列容量并且饱和时也会被拒绝。
在这两种情况下,execute方法会调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)
方法的RejectedExecutionHandler
。
提供了四种预定义的处理程序策略:
- 在默认的
ThreadPoolExecutor.AbortPolicy
中,处理程序在拒绝时抛出运行时RejectedExecutionException
。 - 在
ThreadPoolExecutor.CallerRunsPolicy
中,调用execute的线程本身运行任务。这提供了一种简单的反馈控制机制,可以减缓新任务提交的速度。 - 在
ThreadPoolExecutor.DiscardPolicy
中,无法执行的任务将被简单丢弃。 - 在
ThreadPoolExecutor.DiscardOldestPolicy
中,如果执行器未关闭,则丢弃工作队列头部的任务,然后重试执行(这可能再次失败,导致重复此过程)。
可以定义和使用其他类型的RejectedExecutionHandler
类。这样做需要一些小心,特别是当策略设计仅在特定容量或排队策略下工作时。
钩子方法
该类提供了可保护的可重写的beforeExecute(Thread, Runnable)
和afterExecute(Runnable, Throwable)
方法,这些方法在每个任务执行之前和之后被调用。这些方法可以用于操作执行环境;
例如,重新初始化ThreadLocals
、收集统计信息或添加日志条目。
此外,方法terminated()
可以被重写,以执行在Executor完全终止后需要执行的任何特殊处理。
如果钩子或回调方法抛出异常,内部工作线程可能会失败并突然终止。
队列维护
方法getQueue()
允许访问工作队列,以便进行监视和调试。强烈不建议将此方法用于任何其他目的。
提供的两个方法remove(Runnable)
和purge()
可用于在大量排队任务被取消时协助存储回收。
最终化
如果线程池在程序中不再被引用,并且没有剩余的线程,那么线程池将被自动关闭。
如果您想确保即使用户忘记调用shutdown()
,也能回收未引用的池,那么您必须通过设置适当的保持活动时间,使用零核心线程的下界和/或设置allowCoreThreadTimeOut(boolean)
来安排未使用的线程最终死亡。
扩展示例
该类的大多数扩展重写一个或多个受保护的钩子方法。例如,下面是一个添加简单暂停/恢复功能的子类:
class PausableThreadPoolExecutor extends ThreadPoolExecutor {
private boolean isPaused;
private ReentrantLock pauseLock = new ReentrantLock();
private Condition unpaused = pauseLock.newCondition();
public PausableThreadPoolExecutor(...) {
super(...);
}
protected void beforeExecute(Thread t, Runnable r) {
super.beforeExecute(t, r);
pauseLock.lock();
try {
while (isPaused) unpaused.await();
} catch (InterruptedException ie) {
t.interrupt();
} finally {
pauseLock.unlock();
}
}
public void pause() {
pauseLock.lock();
try {
isPaused = true;
} finally {
pauseLock.unlock();
}
}
public void resume() {
pauseLock.lock();
try {
isPaused = false;
unpaused.signalAll();
} finally {
pauseLock.unlock();
}
}
}