ThreadPoolExecutor参数详情以及阻塞队列与拒绝策略分析

本文详细解析了线程池的工作原理,包括ThreadPoolExecutor的构造函数、核心参数及其作用,探讨了不同线程池的创建方式及自定义策略,旨在帮助读者掌握线程池的高效使用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

心血来潮看了看线程池的源码,突然心底奔出一个念头——写篇博客吧,把它记录下来,以便忘记了的时候帮助回忆。然后就有了这篇文章,好了废话不多说,来一起学习下吧。

开始

public class ThreadPoolExecutor extends AbstractExecutorService

这里可以看出来ThreadPoolExecutor 继承了AbstractExecutorService,而AbstractExecutorService实现了ExecutorService接口。一个ExecutorService ,使用可能的几个合并的线程执行每个提交的任务,通常使用Executors工厂方法配置。

线程池能解决那些问题

线程池解决两个不同的问题:由于每个任务的调用开销减少,它们通常在执行大量异步任务时提供改进的性能,并且它们提供了一种限制和管理资源(包括执行一个任务。 每个ThreadPoolExecutor还维护一些基本统计信息,例如已完成任务的数量。

创建线程池的方式

为了在广泛的上下文中有用,此类提供了许多可调参数和可扩展性钩子。但是我们更多的是使用更方便的Executors工厂方法:

  1. Executors.newCachedThreadPool() (无限线程池,具有自动线程回收)
  2. Executors.newFixedThreadPool(int) (固定大小的线程池)
  3. Executors.newSingleThreadExecutor() (单个后台线程)

但是在实际工作中我们一般都是使用自定义线程池。

自定义线程池

自定义线程池就是使用ThreadPoolExecutor的构造函数了,接下来让我们正式来学习下吧。

第一种
public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue) 

在使用给定的初始参数和默认线程工厂创建一个新的ThreadPoolExecutor ,并拒绝执行处理程序。 使用Executors工厂方法之一可能更方便,而不是这种通用构造函数。

第二种

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory)

创建一个新的 ThreadPoolExecutor与给定的初始参数和默认拒绝执行处理程序。可以看见,这个构造函数可以自定义线程工厂。

第三种

public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          RejectedExecutionHandler handler)

创建一个新的 ThreadPoolExecutor与给定的初始参数和默认线程工厂。这一种我们可以自定义拒绝策略。

前三种我们可以知道,线程池其实是有7个参数的。

七大参数

这里贴出最全的构造函数,即七大参数皆可自定义。

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;
    }
  1. corePoolSize——即使空闲时仍保留在池中的线程数,除非设置 allowCoreThreadTimeOut 。
 	/**
     * Core pool size is the minimum number of workers to keep alive
     * (and not allow to time out etc) unless allowCoreThreadTimeOut
     * is set, in which case the minimum is zero.
     */
    private volatile int corePoolSize;
  1. maximumPoolSize——池中允许的最大线程数
 /**
     * Maximum pool size. Note that the actual maximum is internally
     * bounded by CAPACITY.
     */
    private volatile int maximumPoolSize;
  1. keepAliveTime——当线程数大于内核时,这是多余的空闲线程在终止前等待新任务的最大时间。
/**
     * Timeout in nanoseconds for idle threads waiting for work.
     * Threads use this timeout when there are more than corePoolSize
     * present or if allowCoreThreadTimeOut. Otherwise they wait
     * forever for new work.
     */
    private volatile long keepAliveTime;
  1. unit——keepAliveTime参数的时间单位
  2. workQueue——用于在执行任务之前使用的队列。 这个队列将仅保存execute方法提交的Runnable任务。
/**
     * The queue used for holding tasks and handing off to worker
     * threads.  We do not require that workQueue.poll() returning
     * null necessarily means that workQueue.isEmpty(), so rely
     * solely on isEmpty to see if the queue is empty (which we must
     * do for example when deciding whether to transition from
     * SHUTDOWN to TIDYING).  This accommodates special-purpose
     * queues such as DelayQueues for which poll() is allowed to
     * return null even if it may later return non-null when delays
     * expire.
     */
    private final BlockingQueue<Runnable> workQueue;
  1. threadFactory——执行程序创建新线程时使用的工厂
  2. handler —— 执行被阻止时使用的处理程序,因为达到线程限制和队列容量
  /**
     * Handler called when saturated or shutdown in execute.
     */
    private volatile RejectedExecutionHandler handler;

详解

1. 核心和最大池大小

ThreadPoolExecutor将自动调整池大小(见getPoolSize()根据corePoolSize(参见设定的界限) getCorePoolSize() )和maximumPoolSize(参见getMaximumPoolSize() )。 当方法execute(Runnable)中提交了新任务,并且运行的corePoolSize线程少于一个,即使其他工作线程处于空闲状态,也会创建一个新的线程来处理该请求。 如果超过corePoolSize但小于maximumPoolSize线程运行,则仅当队列已满时才会创建一个新线程。 通过将corePoolSize和maximumPoolSize设置为相同,您将创建一个固定大小的线程池。 通过将maximumPoolSize设置为本质上无限制的值(如Integer.MAX_VALUE ,您可以允许池容纳任意数量的并发任务。 最典型的是,核心和最大池大小只能在构建时进行设置,但也可以使用setCorePoolSize(int)和setMaximumPoolSize(int)进行动态 更改 。

2. 按需施工

默认情况下,即使核心线程最初创建并且只有在新任务到达时才启动,但是可以使用方法prestartCoreThread()或prestartAllCoreThreads()动态地覆盖 。 如果您使用非空队列构建池,则可能需要预先提供线程。

3. 创建新线程

新线程使用ThreadFactory创建。 如果没有另外指定,则使用Executors.defaultThreadFactory() ,它创建所有线程与所有相同的ThreadGroup并且具有相同的优先级和非守护进程状态NORM_PRIORITY 。 通过提供不同的ThreadFactory,您可以更改线程的名称,线程组,优先级,守护进程状态等。如果ThreadFactory在从newThread返回null请求时无法创建线程,则执行程序将继续,但可能无法执行任务 线程应该拥有“modifyThread” RuntimePermission 。 如果使用池的工作线程或其他线程不具有此权限,则服务可能会降级:配置更改可能不会及时生效,并且关闭池可能仍处于可能终止但未完成的状态。

4. 存活时间

如果池当前具有多于corePoolSize线程,则如果空闲超过keepAliveTime(见getKeepAliveTime(TimeUnit) ),则多余的线程将被终止。 这提供了当池未被主动使用时减少资源消耗的方法。 如果稍后池变得更加活跃,将构建新的线程。 此参数也可以使用方法setKeepAliveTime(long, TimeUnit)动态更改 。 使用值Long.MAX_VALUE TimeUnit.NANOSECONDS有效地禁用空闲线程在关闭之前终止。 默认情况下,仅当存在多于corePoolSize线程时,保持活动策略才适用。 但是方法allowCoreThreadTimeOut(boolean)也可以用于将这个超时策略应用于核心线程,只要keepAliveTime值不为零。

5. 队列

任何BlockingQueue可用于传送和保留提交的任务。 这个队列的使用与池大小相互作用:

  • 如果少于corePoolSize线程正在运行,Executor总是喜欢添加一个新线程,而不是排队。
  • 如果corePoolSize或更多的线程正在运行,Executor总是喜欢排队请求而不是添加一个新的线程。
  • 如果请求无法排队,则会创建一个新线程,除非这将超出maximumPoolSize,否则任务将被拒绝。

排队一般有三种策略:
(1) 直接切换 一个工作队列的一个很好的默认选择是一个SynchronousQueue ,将任务交给线程,无需另外控制。 在这里,如果没有线程可以立即运行,那么尝试排队任务会失败,因此将构建一个新的线程。 处理可能具有内部依赖关系的请求集时,此策略可避免锁定。 直接切换通常需要无限制的maximumPoolSizes,以避免拒绝新提交的任务。 这反过来允许无限线程增长的可能性,当命令继续以平均速度比他们可以处理的速度更快地到达时。

(2) 无界队列 使用无界队列(例如LinkedBlockingQueue没有预定容量)会导致新的任务,在队列中等待,当所有corePoolSize线程都很忙。 因此,不会再创建corePoolSize线程。 (因此,最大值大小的值没有任何影响。)每个任务完全独立于其他任务时,这可能是适当的,因此任务不会影响其他执行; 例如,在网页服务器中。 虽然这种排队风格可以有助于平滑瞬态突发的请求,但是当命令继续达到的平均速度比可以处理的速度更快时,它承认无界工作队列增长的可能性。

(3) 有边界的队列。 有限队列(例如, ArrayBlockingQueue )有助于在使用有限maxPoolSizes时防止资源耗尽,但可能更难调整和控制。 队列大小和最大池大小可能彼此交易:使用大队列和小型池可以最大限度地减少CPU使用率,OS资源和上下文切换开销,但可能导致人为的低吞吐量。 如果任务频繁阻塞(例如,如果它们是I / O绑定),则系统可能能够安排比您允许的更多线程的时间。 使用小型队列通常需要较大的池大小,这样可以使CPU繁忙,但可能会遇到不可接受的调度开销,这也降低了吞吐量。

6. 拒绝策略

方法execute(Runnable)中提交的新任务将在执行程序关闭时被拒绝 ,并且当执行程序对最大线程和工作队列容量使用有限边界并且饱和时。 在任一情况下, execute方法调用RejectedExecutionHandler.rejectedExecution(Runnable, ThreadPoolExecutor)其的方法RejectedExecutionHandler 。提供了四个预定义的处理程序策略:

  1. 在默认ThreadPoolExecutor.AbortPolicy ,处理程序会引发运行RejectedExecutionException后排斥反应。
  2. 在ThreadPoolExecutor.CallerRunsPolicy中,调用execute本身的线程运行任务。 这提供了一个简单的反馈控制机制,将降低新任务提交的速度。
  3. 在ThreadPoolExecutor.DiscardPolicy中 ,简单地删除无法执行的任务。
  4. 在ThreadPoolExecutor.DiscardOldestPolicy中 ,如果执行程序没有关闭,则工作队列头部的任务被删除,然后重试执行(可能会再次失败,导致重复)。

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

结语

线程池用到的地方很多,尤其是高并发情况下,在下也只是略懂皮毛,如有不对的地方请指正,咱们一起共同进步。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值