线程池的拒绝策略

目录

拒绝任务的时机

拒绝任务的策略


拒绝任务的时机

  • 第一种情况是当我们调用 shutdown 等方法关闭线程池后,即便此时可能线程池内部依然有没执行完的任务正在执行,但是由于线程池已经关闭,此时如果再向线程池内提交任务,就会遭到拒绝。
  • 第二种情况是线程池没有能力继续处理新提交的任务,也就是工作已经非常饱和的时候。

我们具体讲一下第二种情况,也就是由于工作饱和导致的拒绝。比如新建一个线程池,使用容量上限为 10 的 ArrayBlockingQueue 作为任务队列,并且指定线程池的核心线程数为 5,最大线程数为 10,假设此时有 20 个耗时任务被提交,在这种情况下,线程池会首先创建核心数量的线程,也就是5个线程来执行任务,然后往队列里去放任务,队列的 10 个容量被放满了之后,会继续创建新线程,直到达到最大线程数 10。此时线程池中一共有 20 个任务,其中 10 个任务正在被 10 个线程执行,还有 10 个任务在任务队列中等待,而且由于线程池的最大线程数量就是 10,所以已经不能再增加更多的线程来帮忙处理任务了,这就意味着此时线程池工作饱和,这个时候再提交新任务时就会被拒绝。

拒绝任务的策略

AbortPolicy:丢弃任务并抛出RejectedExecutionException异常

这种拒绝策略在拒绝任务时,会直接抛出一个类型为 RejectedExecutionException 的 RuntimeException,让你感知到任务被拒绝了,于是你便可以根据业务逻辑选择重试或者放弃提交等策略。

DiscardPolicy:丢弃任务,但是不抛出异常

这种拒绝策略正如它的名字所描述的一样,当新任务被提交后直接被丢弃掉,也不会给你任何的通知,相对而言存在一定的风险,因为我们提交的时候根本不知道这个任务会被丢弃,可能造成数据丢失。

DiscardOldestPolicy:丢弃队列最前面的任务,然后重新提交被拒绝的任务

如果线程池没被关闭且没有能力执行,则会丢弃任务队列中的头结点,通常是存活时间最长的任务,这种策略与第二种不同之处在于它丢弃的不是最新提交的,而是队列中存活时间最长的,这样就可以腾出空间给新提交的任务,但同理它也存在一定的数据丢失风险。

CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务

相对而言它就比较完善了,当有新任务提交后,如果线程池没被关闭且没有能力执行,则把这个任务交于提交任务的线程执行,也就是谁提交任务,谁就负责执行任务。这样做主要有两点好处。

  • 第一点新提交的任务不会被丢弃,这样也就不会造成业务损失。
  • 第二点好处是,由于谁提交任务谁就要负责执行任务,这样提交任务的线程就得负责执行任务,而执行任务又是比较耗时的,在这段期间,提交任务的线程被占用,也就不会再提交新的任务,减缓了任务提交的速度,相当于是一个负反馈。在此期间,线程池中的线程也可以充分利用这段时间来执行掉一部分任务,腾出一定的空间,相当于是给了线程池一定的缓冲期。
### Java线程池拒绝策略概述 Java线程池提供了多种内置的拒绝策略来处理任务提交超出线程池容量的情况。这些策略可以通过`ThreadPoolExecutor`类进行设置,具体实现依赖于`RejectedExecutionHandler`接口。 #### 内置拒绝策略详解 1. **AbortPolicy** 默认的拒绝策略,在无法执行新任务时会抛出`RejectedExecutionException`异常[^4]。这种方式适用于希望程序立即失败并报告错误的场景。 2. **CallerRunsPolicy** 当任务被拒绝时,该策略会让提交任务的线程(即调用者线程)执行这个任务。这可能会降低系统的整体吞吐量,但在某些情况下可以缓解压力。 3. **DiscardPolicy** 静默丢弃被拒绝的任务而不做任何通知或记录。这种策略适合那些允许丢失部分任务的应用场景。 4. **DiscardOldestPolicy** 试图丢弃最旧的一个请求以便为当前任务腾出空间。此策略可能会影响较早提交的任务完成时间。 #### 自定义拒绝策略 除了上述四种标准策略外,还可以通过继承`RejectedExecutionHandler`接口来自定义拒绝逻辑。例如,`AbortPolicyWithReport`扩展了默认的`AbortPolicy`,可以在拒绝任务的同时提供额外的日志或其他操作支持[^2]。 以下是创建自定义拒绝策略的一个简单例子: ```java public class CustomRejectPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.err.println("Task " + r.toString() + " rejected from " + executor.toString()); // 可在此处添加其他处理逻辑,比如保存到队列或者日志记录 } } ``` #### 设置拒绝策略的方法 要在线程池中应用特定的拒绝策略,需在初始化`ThreadPoolExecutor`实例时指定它。下面是一个完整的示例代码片段展示如何配置带有不同拒绝策略线程池: ```java import java.util.concurrent.*; public class ThreadPoolExample { public static void main(String[] args) { int corePoolSize = 2; int maximumPoolSize = 4; long keepAliveTime = 10L; TimeUnit unit = TimeUnit.SECONDS; BlockingQueue<Runnable> workQueue = new LinkedBlockingQueue<>(2); // 创建具有CallerRunsPolicy拒绝策略线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, new ThreadPoolExecutor.CallerRunsPolicy() ); // 提交超过线程池承载能力的任务数测试效果 for (int i = 0; i < 10; i++) { final int taskNumber = i; threadPool.submit(() -> { try { Thread.sleep(2000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } System.out.println("Executing Task " + taskNumber); }); } threadPool.shutdown(); } } ``` #### 最佳实践建议 为了更高效地利用线程池资源并减少因拒绝策略引发的问题,应遵循以下几点最佳实践: - 合理设定核心线程数(`corePoolSize`)和最大线程数(`maximumPoolSize`)以匹配实际负载需求[^3]。 - 调整阻塞队列大小(`workQueue`)以平衡内存占用与响应速度之间的关系。 - 定期监控线程池运行状况,包括活动线程计数、已完成任务总数等指标,并据此动态调整参数。 - 根据业务特点选择合适的拒绝策略;如果需要更高的灵活性,则考虑开发定制化解决方案。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值