【Java并发编程】线程池的四种拒绝策略(饱和策略)

引入

线程池的拒绝策略是当线程池出现以下情况时,由于线程池达到其容量上限而无法接受新任务时的处理机制:

  1. 线程池已满:当线程池中的所有线程都在执行任务时,新提交的任务无法立即执行。这种情况发生在当前线程池的核心线程和最大线程数都已被占用时。
  2. 任务队列已满 :当线程池的任务队列(如 LinkedBlockingQueue)已满,新的任务无法被放入队列中等待执行。

在 Java 的并发包(java.util.concurrent)中,ThreadPoolExecutor 提供了四种内置的线程池拒绝策略 (RejectedExecutionHandler)来应对线程池达到其容量限制时,采取适当的策略来处理这些额外的任务。

四种内置拒绝策略适用场景如下,在挑选拒绝策略时需要根据具体的需求选择合适的策略:

策略 说明 适用场景
AbortPolicy 抛出 RejectedExecutionException 任务执行必须完成,不能丢失任务的场景,如实时数据处理系统。
CallerRunsPolicy 调用 execute() 方法在调用线程中运行任务 任务提交者自己执行任务,适用于负载较低时避免任务丢失的场景。
DiscardPolicy 忽略被拒绝的任务,不做任何处理 任务丢失可以接受,优先处理重要任务的场景,如缓存更新。
DiscardOldestPolicy 丢弃队列中最旧的任务并尝试提交新任务 需要保留最新任务,但可以丢弃较旧任务的场景,如短时间内的数据采集。

线程池的四种拒绝策略

1. AbortPolicy

默认的拒绝策略。如果线程池和队列都满了,并且无法接受新任务时,AbortPolicy 策略会直接终止任务提交并抛出 RejectedExecutionException 异常。

java 复制代码
new ThreadPoolExecutor.AbortPolicy();

使用这种策略时,调用者需要处理异常,确保程序不会因为任务被拒绝而崩溃。适用于任务提交者可以控制和处理异常的场景。

示例

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 2, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(2),
    new ThreadPoolExecutor.AbortPolicy()
);

2. CallerRunsPolicy

当任务被拒绝时,CallerRunsPolicy 策略会由任务提交者线程来执行被拒绝的任务,而不是由线程池中的线程来执行。

java 复制代码
new ThreadPoolExecutor.CallerRunsPolicy();

这种策略可以减缓任务提交的速度,避免过度负荷线程池。适用于希望通过降低提交速率来缓解线程池压力的场景。

示例

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 2, 60, TimeUnit.SECONDS,
    new LinkedBlockingQueue<>(2),
    new ThreadPoolExecutor.CallerRunsPolicy()
);

3. DiscardPolicy

当任务被拒绝时,DiscardPolicy 策略会将被拒绝的任务丢弃,没有任何通知或异常抛出。

java 复制代码
new ThreadPoolExecutor.DiscardPolicy();

这种策略采取丢弃任务的方式可能会导致任务丢失,但有助于防止线程池崩溃或性能严重下降,适用于对丢弃任务没有特别要求的场景。

示例

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 2, 60, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(2),
    new ThreadPoolExecutor.DiscardPolicy()
);

4. DiscardOldestPolicy

如果线程池和队列都满了,DiscardOldestPolicy 会丢弃任务队列中最旧的任务,然后尝试提交新的任务。

java 复制代码
new ThreadPoolExecutor.DiscardOldestPolicy();

这种策略可以确保新的任务被处理,但会丢弃最早的任务,可能会丢失一些重要任务。适用于希望保留最新任务的场景。

示例

java 复制代码
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    2, 2, 0L, TimeUnit.MILLISECONDS,
    new LinkedBlockingQueue<>(2),
    new ThreadPoolExecutor.DiscardOldestPolicy()
);

自定义拒绝策略

除了内置的策略,我们可以通过实现自定义的 RejectedExecutionHandler 接口来创建自定义拒绝策略。

java 复制代码
public class CustomRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 自定义处理逻辑,比如记录日志、重新排队等
        System.out.println("任务被拒绝: " + r.toString());
    }
}

例如,我们可以实现一个自定义的拒绝策略,当任务被拒绝时会尝试重试提交任务:

java 复制代码
public class RequeueRejectedExecutionHandler implements RejectedExecutionHandler {

    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 将被拒绝的任务重新排队
        System.out.println("任务被拒绝,正在重新排队: " + r.toString());

        // 尝试将任务重新提交到线程池
        if (!executor.isShutdown()) {
            try {
                Thread.sleep(1000);
                // 直接调用execute方法进行重新排队
                executor.execute(r);
            } catch (Exception e) {
                System.err.println("重新提交任务失败: " + e.getMessage());
            }
        }
    }
}

但是在实际应用中,我们应避免任务被无限重试,应该确保在达到最大重试次数后,任务能够被丢弃或者按照某种策略处理。

相关推荐
嘿嘿-664 小时前
Windows 一键使用 GPT-6 Astra:Codex CLI 配置教程
java·人工智能·windows·gpt·chatgpt·web
是立不是利5 小时前
写给设计师的 CSS 博客美学指南
前端·css
默_笙5 小时前
🏝 Docker 就是"房地产开发":从施工图纸到小区物业的容器化指南
前端·javascript
计算机魔术师5 小时前
Rohan Paul 谈用时间跨度衡量智能体能力,并引用 OpenAI 自动化研究实习生里程碑
前端
kyriewen5 小时前
我手写了个极简版 React Router——才搞懂 v6 为什么砍了这么多 API
前端·javascript·程序员
策案案案5 小时前
YOLO: 将AI Agents嵌入到IntelliJ IDEA
java·大数据·人工智能·笔记·yolo·microsoft·intellij-idea
a1117765 小时前
Shirone 二次元博客主题 开源
前端·开源
IT_陈寒6 小时前
Python的切片赋值把我坑惨了,这不是bug是特性
前端·人工智能·后端
掘金酱6 小时前
【社区公告】致每一位掘友:关于这次调整, 想再说几句
前端·人工智能
CoderLiu6 小时前
程序化工具调用(PTC)与动态工作流引擎:深入大模型工具调用的架构演进与实践
前端·人工智能·后端