Java线程池的管理和控制

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都可以用于保存提交的任务,使用队列的使用和线程池大小进行交互:

  1. 如果运行的线程数少于corePoolSize,则执行器总是添加新线程来处理提交的任务,而不是排队任务。
  2. 如果运行的线程数大于等于corePoolSize,则执行器总是对提交的任务进行排队,而不是添加新线程。
  3. 如果任务队列满了,如果当前线程数小于maximumPoolSize,那么会创建新的线程,如果线程数也达到maximumPoolSize,那么会拒绝提交任务。

有三种任务排队策略:

  1. 直接切换。这种工作队列的一个很好的默认选择是SynchronousQueue,它将任务传递给线程,而不保留它们。在这里,如果没有立即可用的线程来运行它,那么排队一个任务的尝试将会失败,因此将构造一个新的线程。此策略可在处理可能具有内部依赖关系的请求集时避免锁定。直接切换通常需要无限的maximumPoolSizes,以避免拒绝新提交的任务。这反过来要求接受,当请求到达的平均速度比处理的速度快时,可能会要无限增长的线程数。
  2. 无限制的队列。使用无限制队列(例如没有预定义容量的LinkedBlockingQueue)会在所有corePoolSize线程都忙时导致新任务在队列中等待,不会创建比corePoolSize更多的线程,因此,maximumPoolSize的值没有任何影响。适用于每个任务都完全独立于其他任务的场景,因此任务不能相互影响彼此的执行。虽然这种类型的排队对于平滑请求的短暂爆发很有用,但当命令到达的平均速度比处理速度快时,可能会有无限增长的工作队列。
  3. 有界队列。有界队列(例如ArrayBlockingQueue)在使用有限的maximumPoolSize时有助于防止资源耗尽,但可能更难调整和控制。队列大小和最大池大小可以互相平衡折中:使用大队列和小池可以将CPU使用、操作系统资源和上下文切换开销降到最低,但可能会导致人为的降低吞吐量。如果任务经常阻塞(例如,如果是I/O相关的操作),系统可能会调度比预期更多的CPU时间给线程。使用小队列通常需要更大的线程池,这使CPU更忙碌,但可能会遇到不可接受的调度开销,这也会降低吞吐量。​​​​​​​​​​​

2.6 拒收任务

如果执行器(Executor)已经被shutdown,或者执行器使用有界队列和有限的maximumPoolSize,并且队列已经满了,那么使用方法execute(Runnable)提交新任务时会被拒绝。在上述的任何一种场景中,execute方法会调用执行器的RejectedExecutionHandler的rejectedExecution(Runnable, ThreadPoolExecutor)方法,提供了四个预定义的处理策略:

  1. ThreadPoolExecutor.AbortPolicy,默认策略,处理程序在拒绝时抛出运行时异常RejectedExecutionException。
  2. ThreadPoolExecutor.CallerRunsPolicy,调用execute方法的线程本身会运行该任务。这提供了一个简单的反馈控制机制,它将降低提交新任务的速度。
  3. ThreadPoolExecutor.DiscardPolicy,即简单地丢弃不能执行的任务。
  4. 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),使未使用的线程最终终止。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kingspider-sh

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值