【JAVA】JAVA线程池——ThreadPoolExecutor

本文详细解析了Java线程池ThreadPoolExecutor的构造方法、运行原理及排队策略,包括核心线程数量、最大线程数量、空闲线程等待时间、任务队列类型、线程创建工厂和拒绝策略等关键参数的作用,以及如何通过不同BlockingQueue实现不同的排队策略。同时,文章解释了如何判定空闲线程和线程退出机制。

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

一  构造方法

ThreadPoolExecutor的完整构造方法在JDK中的说明如下:

	public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler)
用给定的初始参数创建新的 ThreadPoolExecutor

参数:
corePoolSize - 池中所保存的线程数,包括空闲线程。
maximumPoolSize - 池中允许的最大线程数。
keepAliveTime - 当线程数大于核心时,此为终止前多余的空闲线程等待新任务的最长时间。
unit - keepAliveTime 参数的时间单位。
workQueue - 执行前用于保持任务的队列。此队列仅保持由 execute 方法提交的 Runnable 任务。
threadFactory - 执行程序创建新线程时使用的工厂。
handler - 由于超出线程范围和队列容量而使执行被阻塞时所使用的处理程序。 


二 ThreadPoolExecutor的处理原则

2.1 运行原理

ThreadPoolExecutor处理过程中,主要使用到的容器就是 池 和 排队队列,通过池 和 队列实现线程的调度工作。

    ——池:使用HashSet来存放线程

       ——排队队列:BlockingQueue


通过查看ThreadPoolExecutor的execute方法源码,总结出该方法遵循如下处理逻辑:

    1   尝试将提交的线程加入线程池,成功,则退出,否则,进入步骤2

    2   尝试将线程加入排队队列,成功,则退出,否则,进入步骤3

    3   重新尝试将线程加入线程池,成功,则退出,否则,进入步骤4

    4   进行reject处理(参见reject策略)


说明:

   【1】 尝试加入线程池失败包括"线程池当前线程数大于等于corePoolSize" 和"加入池操作失败"两种情况。(加入池HashSet是同步操作,加入前会判断当前池的容量是否仍小于corePoolSize,并判断当前池的状态是否是RUNNING)

   【2】 尝试加入队列时,会校验线程池当前的状态是否为RUNNING

   【3】 加入排队队列成功后,execute方法会进行保障工作:a) 当前线程池处在非RUNNING状态,则将本轮执行的线程(注意仅本次execute的线程)从排队队列中清除;b) 当前线程池在RUNNING状态,并且排队队列不为空时,查看当前线程池是否为空,如果为空,则创建一个空闲线程

   【4】 再次尝试将西安城加入线程池时,execute会判断线程池的状态和当前池的大小是否小于maximumPoolSize



ThreadPoolExecutor的execute方法处理流程图

2.2 排队策略理解

    在学习ThreadPoolExecutor过程中,排队策略对线程的执行顺序的影响是比较难理解的。在JDK说明文档中,列举了3中排队策略,分别对应着不同的BlockingQueue实现。

  1. 直接提交。工作队列的默认选项是SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集时出现锁。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  2. 无界队列。使用无界队列(例如,不具有预定义容量的LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙时新任务在队列中等待。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。
  3. 有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 
    初次看到上述说明,一头雾水,网上搜到的一些说明,也是错误的。通过查看源码,发现ThreadPoolExecutor并未针对不同的BlockingQueue做不同的处理,所谓的排队策略只是通过不同的BlockingQueue的特性决定的。ThreadPoolExecutor是通过BlockingQueue的offer(E e)来插入排队任务的,此操作的JDK说明如下:
 
booleanoffer(E e)
将指定元素插入此队列中(如果立即可行且不会违反容量限制),成功时返回true,如果当前没有可用的空间,则返回false。当使用有容量限制的队列时,此方法通常要优于add(E),后者可能无法插入元素,而只是抛出一个异常。  

注意offer(E e) 方法是不会阻塞等待的,即插入时如果没有空间,则直接返回错误。该方法在不同的BlockingQueue的实现(SynchrousQueue、LinkedBlockingQueue、ArrayBlockingQueue)中,有不同的表现。
——SynchrousQueue:该队列在offer时,要求必须有"人"在等待着取,如果尝试offer时,没有正在等待从队列中取元素的“人”,则直接返回false。
——LinkedBlockingQueue:该队列通过链表实现,因此是一个无界队列。
——ArrayBlockingQueue:该队列通过数组实现,限制队列的容量,当offer时,如果队列已满,则直接返回false。

了解了不同BlockingQueue的特性后,排队策略也就不难理解了。
——直接提交(SynchrousQueue):execute方法调用offer方法加入排队队列时,如果有空闲线程从排队队列取任务,那么任务将被池中的空闲线程执行,否则加入直接失败,进而会再次尝试直接创建线程放到池中运行。因此,直接提交策略,可以保证顺序提交的任务A、B,一定按提交顺序执行。

——无界队列(LinkedBlockingQueue):该队列是一个无界队列,所以在线程池处在运行状态,尝试将任务加入排队队列时,一定会加入成功(除非内存爆掉),因此就不存在重试加入池中的步骤,进而不会涉及到maximumPoolSize的判断,故此策略下,maximumPoolSize参数无效。
——ArrayBlockingQueue:该队列有界,加入失败时,会尝试加入池,因此需要根据应用场景平衡好池的大小和队列大小。

2.3 如何判定空闲线程?空闲线程如何退出?

    其实,ThreadPoolExecutor并没有设置一个标识位来标识线程的状态,而是由线程自己完成判断和退出动作。当在线程池中运行的线程执行完任务后,会尝试从排队队列中取任务,取任务动作结束后,判断是否取到任务,如果取到任务,则在池中运行任务,否则认为自己是超时的空闲线程,退出。

    线程从排队队列中取任务的方式依赖于"前线程池的线程数是否大于或等于corePoolSize",如果大于或等于,那么线程会通过poll(keepAliveTime, TimeUnit.NANOSECONDS)来从队列中取任务,等待超时就无法取到任务。如果线程数小于corePoolSize时,线程会通过take()命令(取并一直阻塞到取到)取任务。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值