线程池原理–拒绝策略之RejectedExecutionHandler类

本文深入探讨了Java线程池中的拒绝策略,包括CallerRunsPolicy、AbortPolicy、DiscardPolicy和DiscardOldestPolicy等四种主要策略。通过分析RejectedExecutionHandler接口的实现,帮助读者理解当线程池满载时,不同策略如何处理额外任务。

线程池原理–总索引

线程池原理–拒绝策略之RejectedExecutionHandler类

RejectedExecutionHandler接口用于定义当向线程池添加任务时,如果队列已经满了,该做如何处理。

ThreadPoolExecutor类中的相关方法

//RejectedExecutionHandler 属性
private volatile RejectedExecutionHandler handler;

//提交任务
public void execute(Runnable command) {
 if (command == null)
        throw new NullPointerException();
    int c = ctl.get();
    if (workerCountOf(c) < corePoolSize) {
        if (addWorker(command, true))
            return;
        c = ctl.get();
    }
    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);
    }
}
//调用的是 RejectedExecutionHandler 类的 rejectedExecution方法()
final void reject(Runnable command) {
   handler.rejectedExecution(command, this);
}

RejectedExecutionHandler 接口

public interface RejectedExecutionHandler {
    void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}

RejectedExecutionHandler 接口有四个实现类,作为ThreadPoolExecutor类的内部类。

CallerRunsPolicy

如果线程池没有Shutdown,则直接调用Runnable任务的run()方法。

    public static class CallerRunsPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code CallerRunsPolicy}.
         */
        public CallerRunsPolicy() { }

        /**
         * Executes task r in the caller's thread, unless the executor
         * has been shut down, in which case the task is discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                r.run();
            }
        }
    }

AbortPolicy

丢掉这个任务并抛出 RejectedExecutionException异常。

    public static class AbortPolicy implements RejectedExecutionHandler {
        /**
         * Creates an {@code AbortPolicy}.
         */
        public AbortPolicy() { }

        /**
         * Always throws RejectedExecutionException.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         * @throws RejectedExecutionException always
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            throw new RejectedExecutionException("Task " + r.toString() +
                                                 " rejected from " +
                                                 e.toString());
        }
    }

DiscardPolicy

忽略这个任务,什么也不做。

    public static class DiscardPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardPolicy}.
         */
        public DiscardPolicy() { }

        /**
         * Does nothing, which has the effect of discarding task r.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
        }
    }

DiscardOldestPolicy

这个策略从字面上也很好理解,丢弃最老的。也就是说如果队列满了,会将最早进入队列的任务删掉腾出空间,再尝试加入队列。
因为队列是队尾进,队头出,所以队头元素是最老的,因此每次都是移除对头元素后再尝试入队.

    public static class DiscardOldestPolicy implements RejectedExecutionHandler {
        /**
         * Creates a {@code DiscardOldestPolicy} for the given executor.
         */
        public DiscardOldestPolicy() { }

        /**
         * Obtains and ignores the next task that the executor
         * would otherwise execute, if one is immediately available,
         * and then retries execution of task r, unless the executor
         * is shut down, in which case task r is instead discarded.
         *
         * @param r the runnable task requested to be executed
         * @param e the executor attempting to execute this task
         */
        public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
            if (!e.isShutdown()) {
                //去除最早的任务
                e.getQueue().poll();
                //重新向队列中添加该任务
                e.execute(r);
            }
        }
    }

自定义处理类

除了以上的四种,也可以自己实现RejectedExecutionHandler接口,需要实现rejectedExecution方法的逻辑。
并使用ThreadPoolExecutor对象的setRejectedExecutionHandler()方法设置值即可。

public void setRejectedExecutionHandler(RejectedExecutionHandler handler) {
        if (handler == null)
            throw new NullPointerException();
        this.handler = handler;
    }
### 线程池的工作原理 线程池是一种用于管理和复用线程资源的机制,旨在提升系统的性能和响应速度。线程池的核心思想是通过预先创建一定数量的线程,并将这些线程保存在池中,以便重复使用来处理多个任务。线程池通常包含以下几个关键组成部分: 1. **工作线程集合(Workers)**:由若干线程组成,负责执行提交的任务。 2. **任务队列(BlockingQueue)**:用于缓存尚未执行的任务,常见的实现包括 `LinkedBlockingQueue` 和 `ArrayBlockingQueue`。 3. **调度策略**:根据当前线程数和任务队列的状态决定如何分配任务给线程。 4. **拒绝策略RejectedExecutionHandler)**:当线程池无法继续处理任务时,采取的应对措施。 线程池的主要优势在于减少了频繁创建和销毁线程的开销,同时能够有效控制并发线程的数量,从而避免系统资源耗尽的风险。 #### 线程池的任务调度流程 1. 当有新任务提交到线程池时,如果当前运行的线程数少于核心线程数(corePoolSize),则创建一个新的线程来执行该任务。 2. 如果当前运行的线程数已经达到或超过 corePoolSize,则尝试将任务放入任务队列中等待执行。 3. 如果任务队列已满且当前运行的线程数小于最大线程数(maximumPoolSize),则创建新的线程来处理任务。 4. 如果任务队列已满且当前运行的线程数等于 maximumPoolSize,则触发拒绝策略拒绝处理该任务。 ### 线程池的任务拒绝策略详解 当线程池达到其容量上限时,需要一种机制来处理无法接受的新任务,这就是“拒绝策略”。Java 中的 `ThreadPoolExecutor` 提供了四种内置的拒绝策略,并允许开发者自定义拒绝策略。 #### 内置拒绝策略 1. **AbortPolicy**:这是默认的拒绝策略。当任务无法被提交时,直接抛出 `RejectedExecutionException` 异常,阻止系统正常工作[^3]。 2. **CallerRunsPolicy**:由调用 `execute` 方法的线程(通常是主线程)自行执行该任务。这种方式可以减缓任务提交的速度,适用于某些对任务丢失不敏感的场景[^3]。 3. **DiscardPolicy**:直接丢弃无法处理的任务,不做任何处理。这种策略可能导致任务丢失,适用于对任务完整性要求不高的场景[^3]。 4. **DiscardOldestPolicy**:当任务队列已满时,丢弃队列中最旧的任务(即最早进入队列的任务),然后尝试重新提交当前任务。需要注意的是,这种策略可能会导致部分任务被意外丢弃,因此使用时需谨慎[^5]。 #### 自定义拒绝策略 除了上述内置策略外,开发者还可以通过实现 `RejectedExecutionHandler` 接口来自定义拒绝策略。自定义策略可以根据具体业务需求进行灵活处理,例如记录日志、发送警报或者将任务持久化以备后续处理。 ```java public class CustomRejectionPolicy implements RejectedExecutionHandler { @Override public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) { System.out.println("Task " + r.toString() + " is rejected."); // 可以在此处添加其他逻辑,如记录日志或发送通知 } } ``` 在实际应用中,选择合适的拒绝策略非常重要。不同的业务场景可能需要不同的策略。例如,在高并发环境下,可以选择 `DiscardOldestPolicy` 或 `CallerRunsPolicy` 来缓解压力;而在对数据完整性要求较高的系统中,`AbortPolicy` 更为合适,因为它会明确地告知调用者任务未能成功提交。 ### 配置线程池时的注意事项 - **合理设置核心线程数与最大线程数**:根据系统的负载能力和预期的并发量进行调整。 - **选择合适型的任务队列**:根据任务的性质(如是否允许阻塞、是否有优先级区分等)选择适当的队列实现。 - **正确配置拒绝策略**:确保在极端情况下系统仍能保持稳定运行,同时尽量减少任务丢失的风险。 - **监控线程池状态**:定期检查线程池的活跃线程数、队列长度等指标,及时发现潜在问题并作出调整。 通过以上分析可以看出,线程池不仅提高了多线程程序的效率,还提供了丰富的配置选项以适应各种复杂的应用场景。理解其工作原理拒绝策略对于构建高效稳定的并发系统至关重要。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值