1、ExecutorService
线程池的接口为java.util.concurrent.ExecutorService,里面的方法行为约定如下:
- void shutdown()
发起一个有序的停止,先前已经提交的任务会被执行,并拒绝提交新的任务。
重复调用shutdown不会有副作用,遵从幂等性原则。
该方法立即返回,不会等待先前提交的任务执行完成,应该使用awaitTermination方法去等待已提交任务执行完成。
- List<Runnable> shutdownNow()
试图停止所有的正在执行中的任务,停止处理等待中的任务,并返回等待中的任务列表。
该方法不会等待正在执行中的任务停止,应该使用awaitTermination方法来等待执行中的任务停止。
除了尽最大努力停止处理正在执行的任务之外,没有任何保证。例如,典型的实现将通过Thread.interrupt取消线程,因此任何无法响应中断的任务都可能永远不会终止。
- boolean awaitTermination(long timeout, TimeUnit unit)
在shutdown之后,等待所有的任务执行完成,或者发生了超时。
返回true,表示所有的任务在超时发生前都完成了;返回false,表示发生了超时。
下面的示例代码是一个典型的停止ExecutorService的处理逻辑,分两个阶段关闭执行者服务,首先是调用“关闭”以拒绝传入新的任务,如果有必要,然后调用“立即关闭”以取消任何延迟的任务:
void shutdownAndAwaitTermination(ExecutorService pool) {
pool.shutdown(); // Disable new tasks from being submitted
try {
// Wait a while for existing tasks to terminate
if (!pool.awaitTermination(60, TimeUnit.SECONDS)) {
pool.shutdownNow(); // Cancel currently executing tasks
// Wait a while for tasks to respond to being cancelled
if (!pool.awaitTermination(60, TimeUnit.SECONDS))
System.err.println("Pool did not terminate");
}
} catch (InterruptedException ie) {
// (Re-)Cancel if current thread also interrupted
pool.shutdownNow();
// Preserve interrupt status
Thread.currentThread().interrupt();
}
}
- boolean isShutdown()
查询执行者服务是否已经被shutdown。
- boolean isTerminated()
查询shutdown之后,是否所有的任务都已经完成了。
通常在shutdown或者shutdownNow调用之后调用,否则总是返回false。
2、ThreadPoolExecutor
2.1 corePoolSize和maximumPoolSize
线程池执行器将根据corePoolSize和maximumPoolSize调整线程池的大小。
当在方法execute(Runnable)中提交新任务时,并且运行线程数少于corePoolSize时,将创建一个新线程来处理请求,即使其他工作线程空闲。
如果运行的线程数超过corePoolSize,但小于maximumPoolSize,则只有在任务队列已满时才会创建一个新线程。
通过设置corePoolSize和maximumPoolSize相同值,可以创建一个固定大小的线程池。
通过将最大池大小设置为一个实质上无界的值,如Integer.MAX_VALUE时,则允许池容纳任意数量的并发任务。
通常,核心和最大池大小只在构建时设置,但也可以使用setCorePoolSize和setMaximumPoolSize进行动态更改。
2.2 按需创建线程
默认情况下,即使是corePoolSize的线程数也是在新任务到达的时候被创建。但是,可以使用方法prestartCoreThread 或者 prestartAllCoreThreads改变该默认行为,例如在创建线程池的时候传入了一个非空的任务队列,需要有线程立即执行。
2.3 创建新线程
使用线程工厂(ThreadFactory)创建新的线程。如果未专门指定,则使用Executors.defaultThreadFactory,创建的所有线程位于同一线程组(ThreadGroup)中,且具有相同NORM_PRIORITY优先级和非守护状态的线程。
通过提供不同的线程工厂,可以更改线程的名称、线程组、优先级、守护状态等。如果线程工厂的newThread方法返回null,没有创建出线程,执行者将继续,但可能无法执行任何任务。
线程应该具有“modifyThread”的运行时权限(RuntimePermission)。如果使用该池的工作线程或其他线程不具有此权限,则服务可能会降级:配置更改可能不会及时生效,并且shutdown线程池可能仍处于可能终止但未完成的状态。
2.5 线程存活时间
如果池中的线程数超过了corePoolSize,则如果多余的线程空闲时间超过keepAliveTime,则会终止。这提供了一种在未主动使用池时减少资源消耗的方法。如果池稍后变得更加活跃,则将构造新的线程。
可以使用方法setKeepAliveTime(long, TimeUnit)动态更改存活时间参数。使用Long.MAX_VALUE TimeUnit.NANOSECONDS将会有效地禁用空闲线程被终止。
默认情况下,保持存活策略仅适用于线程数超过corePoolSize时。但是方法allowCoreThreadTimeOut(boolean)也可以用来将这个超时策略应用于核心线程,只要keepAlive时间值非零。
任务排队
任何BlockingQueue都可以用于保存提交的任务,使用队列的使用和线程池大小进行交互:
- 如果运行的线程数少于corePoolSize,则执行器总是添加新线程来处理提交的任务,而不是排队任务。
- 如果运行的线程数大于等于corePoolSize,则执行器总是对提交的任务进行排队,而不是添加新线程。
- 如果任务队列满了,如果当前线程数小于maximumPoolSize,那么会创建新的线程,如果线程数也达到maximumPoolSize,那么会拒绝提交任务。
有三种任务排队策略:
- 直接切换。这种工作队列的一个很好的默认选择是SynchronousQueue,它将任务传递给线程,而不保留它们。在这里,如果没有立即可用的线程来运行它,那么排队一个任务的尝试将会失败,因此将构造一个新的线程。此策略可在处理可能具有内部依赖关系的请求集时避免锁定。直接切换通常需要无限的maximumPoolSizes,以避免拒绝新提交的任务。这反过来要求接受,当请求到达的平均速度比处理的速度快时,可能会要无限增长的线程数。
- 无限制的队列。使用无限制队列(例如没有预定义容量的LinkedBlockingQueue)会在所有corePoolSize线程都忙时导致新任务在队列中等待,不会创建比corePoolSize更多的线程,因此,maximumPoolSize的值没有任何影响。适用于每个任务都完全独立于其他任务的场景,因此任务不能相互影响彼此的执行。虽然这种类型的排队对于平滑请求的短暂爆发很有用,但当命令到达的平均速度比处理速度快时,可能会有无限增长的工作队列。
- 有界队列。有界队列(例如ArrayBlockingQueue)在使用有限的maximumPoolSize时有助于防止资源耗尽,但可能更难调整和控制。队列大小和最大池大小可以互相平衡折中:使用大队列和小池可以将CPU使用、操作系统资源和上下文切换开销降到最低,但可能会导致人为的降低吞吐量。如果任务经常阻塞(例如,如果是I/O相关的操作),系统可能会调度比预期更多的CPU时间给线程。使用小队列通常需要更大的线程池,这使CPU更忙碌,但可能会遇到不可接受的调度开销,这也会降低吞吐量。
2.6 拒收任务
如果执行器(Executor)已经被shutdown,或者执行器使用有界队列和有限的maximumPoolSize,并且队列已经满了,那么使用方法execute(Runnable)提交新任务时会被拒绝。在上述的任何一种场景中,execute方法会调用执行器的RejectedExecutionHandler的rejectedExecution(Runnable, ThreadPoolExecutor)方法,提供了四个预定义的处理策略:
- ThreadPoolExecutor.AbortPolicy,默认策略,处理程序在拒绝时抛出运行时异常RejectedExecutionException。
- ThreadPoolExecutor.CallerRunsPolicy,调用execute方法的线程本身会运行该任务。这提供了一个简单的反馈控制机制,它将降低提交新任务的速度。
- ThreadPoolExecutor.DiscardPolicy,即简单地丢弃不能执行的任务。
- ThreadPoolExecutor.DiscardOldestPolicy,如果执行器未shutdown,则删除工作队列前面的任务,然后重试执行(可能再次失败,导致重复执行该策略)。
可以定义和使用其他类型的RejectedExecutionHandler类,需要一些特别注意,特别是当策略被设计为只在特定容量或排队策略下工作时。
2.7 钩子方法
ThreadPoolExecutor类提供了protected的可重写的方法beforeExecute(Thread, Runnable) 和afterExecute(Runnable, Throwable) ,这些方法在执行每个任务之前和之后调用。
这些操作可用于操作执行环境,例如,重新初始化线程的ThreadLocals、收集统计信息或添加日志条目。此外,可以重写terminated方法,以执行Executor完全终止后需要完成的任何特殊处理。
如果钩子或回调方法抛出异常,内部工作线程反过来可能会失败并突然终止。
2.8 队列维护
方法getQueue()允许访问工作队列,以便进行监视和调试,禁止将此方法用于任何其他目的。提供了两个方法remove(Runnable)和purge,可以在大量队列任务被清除时帮助进行存储回收。
2.9 终止线程池
如果线程池不在程序中被引用,并且没有剩余线程,那么将被自动shutdown。如果希望即使用户忘记调用shutdown也可以回收未被引用的线程池,则必须设置corePoolSize为0,并设置allowCoreThreadTimeOut(true),使未使用的线程最终终止。