5.4 拒绝策略实践

先带大家回顾一下策略是如何触发执行的流程:

//添加任务,当不满足条件时会执行拒绝方法reject
public void execute(Runnable command) {
   //...
    if (isRunning(c) && workQueue.offer(command)) {
        int recheck = ctl.get();
        if (! isRunning(recheck) && remove(command))
            reject(command);
        else if (workerCountOf(recheck) == 0)
            addWorker(null, false);
    }
    else if (!addWorker(command, false))
        reject(command);
}


//这里就是拒绝的入口。handler是有构造方法传入
final void reject(Runnable command) {
    handler.rejectedExecution(command, this);
}


public ThreadPoolExecutor(int corePoolSize,
                          int maximumPoolSize,
                          long keepAliveTime,
                          TimeUnit unit,
                          BlockingQueue<Runnable> workQueue,
                          ThreadFactory threadFactory,
                          RejectedExecutionHandler handler) {
    //....
    //指定拒绝策略
    this.handler = handler;
}

AbortPolicy:默认的拒绝策略,简单粗暴,当 execute 中添加 woker 失败时,直接在当前线程抛出异常。

public static class AbortPolicy implements RejectedExecutionHandler {


     //直接抛出RejectedExecutionException
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        throw new RejectedExecutionException("Task " + r.toString() +
                                             " rejected from " +
                                             e.toString());
    }
}

优点:快速失败,立即暴露系统过载问题、避免任务静默丢失,便于监控系统捕获

缺点:需要调用方显式处理异常,增加代码复杂度,可能中断主业务流程

适用场景:适用于那些对任务丢失非常敏感,配合熔断机制使用的快速失败场景

CallerRunsPolicy:提交任务的线程,直接执行任务

public static class CallerRunsPolicy implements RejectedExecutionHandler {
    //直接在提交任务的线程中执行任务
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            r.run();
        }
    }
}

优点:任务都会被执行,不会丢任务,并且由于主线程执行任务,天然的流量控制,避免了大量的任务进入线程池。

缺点:调用线程可能被阻塞,导致上游服务雪崩。不适合高并发场景(可能拖垮整个调用链)。

适用场景:适用于处理能力不高,并且资源过载能够平滑过渡,同时不丢失任务的场景。如:低并发、高可靠性的后台任务(如日志归档)、允许同步执行的批处理系统。

DiscardPolicy:直接丢弃被拒绝的任务,不做任何通知。

public static class DiscardPolicy implements RejectedExecutionHandler {
   //空实现
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
    }
}

优点:实现简单,无额外性能开销。避免异常传播影响主流程

缺点:数据静默丢失,可能会掩盖系统容量问题

适用场景:边缘业务的监控上报数据,统计类的 uv、pv 统计任务

DiscardOldestPolicy:丢弃队列中最旧的任务,然后重试提交当前任务。

public static class DiscardOldestPolicy implements RejectedExecutionHandler {
    //丢弃队列中最旧的任务,重试当前任务
    public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        if (!e.isShutdown()) {
            e.getQueue().poll();
            e.execute(r);
        }
    }
}

优点:优先保证新任务执行,避免队列堆积导致内存溢出。

缺点:可能丢失关键旧任务、任务执行顺序无法保证。

适用场景:适用于可容忍部分数据丢失,并且实时性要求高于历史数据的场景,比如:行情推送。

通过上线的介绍,我们可以看到 JDK 内置策略基本上只使用于简单处理的场景,在生产实践中一般推荐我们自定义拒绝策略,进行相关的业务处理。

1. 自定义 RejectedExecutionHandler

/**
 * 带监控统计的拒绝策略处理器
 */
public class MetricsRejectedExecutionHandler implements RejectedExecutionHandler {
    private static final org.slf4j.Logger logger = org.slf4j.LoggerFactory.getLogger(MetricsRejectedExecutionHandler.class);


    // 统计被拒绝的任务数量
    private final AtomicLong rejectedCount = new AtomicLong(0);


    @Override
    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
        // 1. 采集线程池关键指标
        int poolSize = executor.getPoolSize();
        int activeThreads = executor.getActiveCount();
        int corePoolSize = executor.getCorePoolSize();
        int maxPoolSize = executor.getMaximumPoolSize();
        int queueSize = executor.getQueue().size();
        long completedTasks = executor.getCompletedTaskCount();


        // 2. 递增拒绝计数器
        long totalRejected = rejectedCount.incrementAndGet();


        // 3. 输出警告日志(包含完整指标)
        logger.warn("""
             任务被拒绝执行!线程池状态:
            |- 活跃线程数/当前线程数: {}/{}
            |- 核心/最大线程数: {}/{}
            |- 队列大小: {}
            |- 已完成任务数: {}
            |- 历史拒绝总数: {}
            |- 被拒绝任务: {}
            """, 
            activeThreads, poolSize,
            corePoolSize, maxPoolSize,
            queueSize,
            completedTasks,
            totalRejected,
            r.getClass().getName());


        // 4. 可选:降级处理(如存入数据库等待重试)
        // fallbackToDatabase(r);


        // 5. 抛出RejectedExecutionException(保持默认行为)
        throw new RejectedExecutionException("Task " + r.toString() + " rejected");
    }
    
    // 获取累计拒绝次数(用于监控)
    public long getRejectedCount() {
        return rejectedCount.get();
    }
}
  • 记录日志并告警:所有的异常处理中,最常见简单的方式无外乎,先记录个日志,然后有告警系统的进行相关的某书、某信以及短信等的告警信息推送,方便开发人员以及运维人员的及时发现问题并介入处理。
  • 兜底处理机制:一般常见的就是通过异步的方式提交到 MQ,然后统一进行兜底处理。
  • 带超时和重试的拒绝:可以尝试等待一小段时间,或者重试几次提交,如果仍然失败,再执行最终的拒绝逻辑(如告警、持久化或抛异常)。
  • 动态调整策略:根据系统的负载或任务类型,动态的执行
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值