ThreadPoolExecutor @since 1.5 @author Doug Lea

本文解析了ThreadPoolExecutor类的设计原理,如何通过配置核心池和最大池大小、线程工厂、队列策略来管理和优化线程资源,以及处理拒绝执行任务的方法和注意事项。
摘要由CSDN通过智能技术生成
ThreadPoolExecutor
类的设计思路和目的主要是为了提供一种高效、
灵活且可控的方式来管理和复用线程资源,
以便更好地处理并发任务。

以下是源码中的注释说明:


An ExecutorService that executes each submitted task using one of possibly several pooled threads, typically configured using Executors factory methods.

Thread pools address two distinct issues: they generally offer enhanced performance when processing a large volume of asynchronous tasks, due to reduced per-task overhead, and they offer a mechanism for limiting and managing the resources, including threads, utilized when executing a batch of tasks. Each ThreadPoolExecutor also maintains basic statistics, such as the count of completed tasks.

To cater to a broad range of applications, this class provides numerous configurable parameters and extension points. However, developers are encouraged to utilize the more convenient Executors factory methods: Executors.newCachedThreadPool (an unbounded thread pool with automatic thread reclamation), Executors.newFixedThreadPool (a fixed-size thread pool), and Executors.newSingleThreadExecutor (a single background thread), which pre-configure settings for the most prevalent use cases. Otherwise, refer to the following guidelines when manually configuring and tuning this class:

  • Core and Maximum Pool Sizes: A ThreadPoolExecutor automatically adjusts the pool size (see getPoolSize) according to the thresholds set by corePoolSize (see getCorePoolSize) and maximumPoolSize (see getMaximumPoolSize). When a new task is submitted via the execute(Runnable) method, and fewer than corePoolSize threads are active, a new thread is created to handle the request, even if other worker threads are idle. If there are more than corePoolSize but fewer than maximumPoolSize threads running, a new thread will only be created if the queue is full. By setting corePoolSize and maximumPoolSize to the same value, you establish a fixed-size thread pool. By setting maximumPoolSize to an effectively unbounded value such as Integer.MAX_VALUE, you permit the pool to accommodate an arbitrary number of concurrent tasks. Typically, core and maximum pool sizes are set only upon construction, but they can also be altered dynamically using setCorePoolSize and setMaximumPoolSize.

  • On-demand Construction: By default, even core threads are initially created and started only when new tasks arrive, but this behavior can be overridden dynamically using the methods prestartCoreThread or prestartAllCoreThreads. You may wish to prestart threads if you initialize the pool with a non-empty queue.

  • Creating New Threads: New threads are created using a ThreadFactory. If not otherwise specified, Executors.defaultThreadFactory is used, which generates threads all belonging to the same ThreadGroup with the same NORM_PRIORITY priority and non-daemon status. By providing a custom ThreadFactory, you can modify the thread's name, thread group, priority, daemon status, etc. If a ThreadFactory fails to create a thread, returning null from newThread, the executor will continue but may be unable to execute any tasks. Threads should possess the "modifyThread" RuntimePermission. If worker threads or other threads using the pool lack this permission, service may be compromised: configuration changes may not take effect promptly, and a shutting down pool may remain in a state where termination is possible but not finalized.

  • Keep-alive Times: If the pool currently has more than corePoolSize threads, excess threads will be terminated if they have been idle for more than the keepAliveTime (see getKeepAliveTime(TimeUnit)). This provides a means of reducing resource consumption when the pool is not actively utilized. If the pool becomes busier later, new threads will be created. This parameter can also be changed dynamically using the method setKeepAliveTime(long, TimeUnit). Using a value of Long.MAX_VALUE TimeUnit.NANOSECONDS effectively prevents idle threads from ever terminating before shut down. By default, the keep-alive policy applies only when there are more than corePoolSize threads. However, the method allowCoreThreadTimeOut(boolean) can be used to apply this time-out policy to core threads as well, provided that the keepAliveTime value is non-zero.

  • Queuing: Any BlockingQueue can be employed to transfer and hold submitted tasks. The use of this queue interacts with pool sizing:

    • If fewer than corePoolSize threads are running, the Executor always prefers adding a new thread rather than queuing.
    • If corePoolSize or more threads are running, the Executor always prefers queuing a request rather than adding a new thread.
    • If a request cannot be queued, a new thread is created unless this would exceed maximumPoolSize, in which case, the task will be rejected.

    There are three general queuing strategies:

    • Direct Handoffs: A good default choice for a work queue is a SynchronousQueue that hands off tasks to threads without otherwise holding them. Here, an attempt to queue a task will fail if no threads are immediately available to run it, so a new thread will be constructed. This policy avoids lockups when handling sets of requests that might have internal dependencies. Direct handoffs generally require unbounded maximumPoolSizes to avoid rejection of new submitted tasks. This in turn admits the possibility of unbounded thread growth when commands continue to arrive on average faster than they can be processed.
    • Unbounded Queues: Using an unbounded queue (for example,a LinkedBlockingQueue without a predefined capacity) will cause new tasks to wait in the queue when all corePoolSize threads are busy. Thus, no more than corePoolSize threads will ever be created. (And the value of the maximumPoolSize therefore doesn't have any effect.) This may be appropriate when each task is completely independent of others, so tasks cannot affect each other's execution; for example, in a web page server. While this style of queuing can be useful in smoothing out transient bursts of requests, it admits the possibility of unbounded work queue growth when commands continue to arrive on average faster than they can be processed.
    • Bounded Queues: A bounded queue (for example, an ArrayBlockingQueue) helps prevent resource exhaustion when used with finite maximumPoolSizes, but can be more difficult to tune and control. Queue sizes and maximum pool sizes may be traded off for each other: Using large queues and small pools minimizes CPU usage, OS resources, and context-switching overhead, but can lead to artificially low throughput. If tasks frequently block (for example, if they are I/O bound), a system may be able to schedule time for more threads than you otherwise allow. Use of small queues generally requires larger pool sizes, which keeps CPUs busier but may encounter unacceptable scheduling overhead, which also decreases throughput.
  • Rejected Tasks: New tasks submitted via the execute(Runnable) method will be rejected when the Executor has been shut down, and also when the Executor uses finite bounds for both maximum threads and work queue capacity, and is saturated. In either case, the execute method invokes the RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor) method of its RejectedExecutionHandler. Four predefined handler policies are provided:

    • In the default ThreadPoolExecutor.AbortPolicy, the handler throws a runtime RejectedExecutionException upon rejection.
    • In ThreadPoolExecutor.CallerRunsPolicy, the thread that invokes execute itself runs the task. This provides a simple feedback control mechanism that will slow down the rate that new tasks are submitted.
    • In ThreadPoolExecutor.DiscardPolicy, a task that cannot be executed is simply dropped.
    • In ThreadPoolExecutor.DiscardOldestPolicy, if the executor is not shut down, the task at the head of the work queue is dropped, and then execution is retried (which can fail again, causing this to be repeated).

    It is possible to define and use other kinds of RejectedExecutionHandler classes. Doing so requires some care, especially when policies are designed to work only under particular capacity or queuing policies.

  • Hook Methods: This class provides protected overridable beforeExecute(Thread, Runnable) and afterExecute(Runnable, Throwable) methods that are called before and after the execution of each task. These can be used to manipulate the execution environment; for example, reinitializing ThreadLocals, gathering statistics, or adding log entries. Additionally, the method terminated can be overridden to perform any special processing that needs to be done once the Executor has fully terminated.

    If hook or callback methods throw exceptions, internal worker threads may in turn fail and abruptly terminate.

  • Queue Maintenance: The method getQueue() allows access to the work queue for purposes of monitoring and debugging. Use of this method for any other purpose is strongly discouraged. Two supplied methods, remove(Runnable) and purge, are available to assist in storage reclamation when large numbers of queued tasks become cancelled.

  • Finalization: A pool that is no longer referenced in a program AND has no remaining threads will be shutdown automatically. If you would like to ensure that unreferenced pools are reclaimed even if users forget to call shutdown, then you must arrange that unused threads eventually die, by setting appropriate keep-alive times, using a lower bound of zero core threads and/or setting allowCoreThreadTimeOut(boolean).


ExecutorService 是一个执行服务,它使用一个或多个池化线程来执行每个提交的任务,通常通过 Executors 工厂方法进行配置。

线程池解决了两个问题:它们通常在处理大量异步任务时提供更高的性能,因为减少了每项任务的开销;它们还提供了一种限制和管理执行一批任务时使用的资源(包括线程)的机制。每个 ThreadPoolExecutor 还维护基本的统计信息,例如完成的任务数量。

为了满足广泛的应用需求,这个类提供了许多可配置的参数和扩展点。然而,开发者被鼓励使用更便捷的 Executors 工厂方法:Executors.newCachedThreadPool(一个无界线程池,具有自动线程回收功能)、Executors.newFixedThreadPool(一个固定大小的线程池)和 Executors.newSingleThreadExecutor(一个单后台线程),这些方法为最常见的用例预配置了设置。否则,在手动配置和调整此类时,请参考以下指南:

  • 核心和最大池大小ThreadPoolExecutor 根据 corePoolSize(参见 getCorePoolSize)和 maximumPoolSize(参见 getMaximumPoolSize)设置的阈值自动调整池大小(参见 getPoolSize)。当通过 execute(Runnable) 方法提交新任务时,如果活跃的线程少于 corePoolSize,则会创建一个新线程来处理请求,即使其他工作线程处于空闲状态。如果运行的线程多于 corePoolSize 但少于 maximumPoolSize,则只有在队列满时才会创建新线程。通过将 corePoolSizemaximumPoolSize 设置为相同的值,您可以建立一个固定大小的线程池。通过将 maximumPoolSize 设置为一个实际上无界值,例如 Integer.MAX_VALUE,您可以允许池容纳任意数量的并发任务。通常,核心和最大池大小仅在构造时设置,但也可以动态地使用 setCorePoolSizesetMaximumPoolSize 进行更改。

  • 按需构建:默认情况下,即使是核心线程也只在新任务到达时创建并启动,但这种行为可以使用方法 prestartCoreThreadprestartAllCoreThreads 动态覆盖。如果您希望在使用非空队列初始化池时预启动线程。

  • 创建新线程:新线程的创建使用 ThreadFactory。如果没有特别指定,将使用 Executors.defaultThreadFactory,它生成属于同一个 ThreadGroup、具有相同 NORM_PRIORITY 优先级和非守护状态的所有线程。通过提供自定义的 ThreadFactory,您可以修改线程的名称、线程组、优先级、守护状态等。如果 ThreadFactory 无法创建线程,并从 newThread 返回 null,执行器将继续运行,但可能无法执行任何任务。线程应具备 "modifyThread" RuntimePermission。如果工作线程或其他使用池的线程缺乏此权限,服务可能会受到影响:配置更改可能不会及时生效,正在关闭的池可能仍处于可能终止但未最终确定的状态。

  • 保持活动时间:如果池中的线程数量超过 corePoolSize,超过的线程将在空闲超过 keepAliveTime(参见 getKeepAliveTime(TimeUnit))后被终止。这提供了一种在池未被积极使用时减少资源消耗的手段。如果池后来变得更忙,将创建新线程。这个参数也可以动态地使用方法 setKeepAliveTime(long, TimeUnit) 进行更改。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值有效地防止了在关闭之前空闲线程的终止。默认情况下,保持活动政策仅适用于超过 corePoolSize 的线程。然而,可以使用方法 allowCoreThreadTimeOut(boolean) 将此超时策略应用于核心线程,前提是 keepAliveTime 值非零。

  • 排队:任何 BlockingQueue 都可以用来传递和保存提交的任务。这个队列的使用与池大小有交互:

    • 如果运行的线程少于 corePoolSize,执行器总是更倾向于添加新线程而不是排队。
    • 如果运行的线程达到或超过 corePoolSize,执行器总是更倾向于排队请求而不是添加新线程。
    • 如果无法排队请求,除非这将超过 maximumPoolSize,否则将创建新线程,在这种情况下,任务将被拒绝。

    有三种一般的排队策略:

    • 直接交接:对于工作队列,一个良好的默认选择是 SynchronousQueue,它将任务直接交接给线程,而不保留它们。在这里,如果没有线程立即可用来运行它,尝试排队任务将失败,因此将构造一个新线程。这种策略避免了处理可能具有内部依赖性的请求集时的死锁。直接交接通常需要无界的 maximumPoolSizes 以避免拒绝新提交的任务。这反过来又可能导致线程无限增长的可能性,当命令平均到达速度比它们能被处理的速度快时。
    • 无界队列:使用无界队列(例如,没有预定义容量的 LinkedBlockingQueue)将导致新任务在所有 corePoolSize 线程都忙时在队列中等待。因此,永远不会创建超过 corePoolSize 的线程。(因此,maximumPoolSize 的值没有任何影响。)当每个任务完全独立于其他任务时,这可能是适当的,所以任务不会影响彼此的执行;例如,在网页服务器中。虽然这种风格的排队可以在平滑处理瞬态请求爆发时很有用,但它承认了在命令平均到达速度比它们能被处理的速度快时,工作队列增长无界的可能性。
    • 有界队列:使用有界队列(例如,ArrayBlockingQueue)与有限的 maximumPoolSizes 一起使用,有助于防止资源耗尽,但可能更难以调整和控制。队列大小和最大池大小可以相互交换:使用大队列和小池可以最小化 CPU 使用率、操作系统资源和上下文切换开销,但可能导致人为的低吞吐量。如果任务经常阻塞(例如,如果它们是 I/O 绑定的),系统可能能够为您允许的线程安排更多时间。使用小队列通常需要更大的池大小,这使得 CPU 更忙,但可能会遇到不可接受的调度开销,这也降低了吞吐量。
  • 拒绝任务:当执行器已关闭,以及当执行器对最大线程数和工作队列容量都有有限的界限,并且已饱和时,通过 execute(Runnable) 方法提交的新任务将被 拒绝。在任何情况下,execute 方法都会调用其 RejectedExecutionHandlerrejectedExecution(Runnable, ThreadPoolExecutor) 方法。提供了四种预定义的处理策略:

    • 在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序在拒绝时抛出运行时 RejectedExecutionException
    • 在 ThreadPoolExecutor.CallerRunsPolicy 中,调用 execute 的线程自己运行任务。这提供了一个简单的反馈控制机制,可以减慢新任务提交的速率。
    • 在 ThreadPoolExecutor.DiscardPolicy 中,无法执行的任务被简单地丢弃。
    • 在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行器没有关闭,队列头部的任务将被丢弃,然后重试执行(这可能会再次失败,导致重复此过程)。

    可以定义和使用其他类型的 RejectedExecutionHandler 类。这样做需要小心,特别是当策略被设计为仅在特定容量或排队策略下工作时。

  • 钩子方法:这个类提供了 protected 可覆盖的 beforeExecute(Thread, Runnable)afterExecute(Runnable, Throwable) 方法,它们在每个任务执行之前和之后被调用。这些可以用来操纵执行环境;例如,重新初始化 ThreadLocals、收集统计数据或添加日志条目。此外,方法 terminated 可以被覆盖,以在执行器完全终止后执行任何特殊处理。

    如果钩子或回调方法抛出异常,内部工作线程可能会失败并突然终止。

  • 队列维护:方法 getQueue() 允许访问工作队列,以便进行监控和调试。强烈不鼓励将此方法用于任何其他目的。提供了两种方法,remove(Runnable)purge,以协助在大量排队任务被取消时进行存储回收。

  • 最终化:在程序中不再引用的池 AND 没有剩余线程时,将自动 shutdown。如果您希望确保即使用户忘记调用 shutdown,未引用的池也能被回收,那么您必须安排未使用的线程最终死亡,通过设置适当的保持活动时间、使用零或更低的核心线程下界和/或设置 allowCoreThreadTimeOut(boolean)

  • 17
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

P("Struggler") ?

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

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

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

打赏作者

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

抵扣说明:

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

余额充值