如果你提交任务时,线程池队列已满,这时会发生什么?

2024-11-06 23:23

image-20241106232247952

当你提交任务到线程池(如Java中的ThreadPoolExecutor)时,如果线程池的队列(工作队列,如LinkedBlockingQueue)已满,这时会发生什么取决于你所使用的具体线程池配置及其拒绝策略(RejectedExecutionHandler)。

Java中的ThreadPoolExecutor提供了几种默认的拒绝策略:

  1. AbortPolicy(默认策略)

  2. 如果队列已满并且线程池中的线程数达到了最大线程数,那么ThreadPoolExecutor会抛出RejectedExecutionException,任务不会被执行。

  3. CallerRunsPolicy

  4. 如果队列已满,任务将由调用execute方法的线程(即提交任务的线程)直接运行。这提供了一种降级的机制,可以降低新任务的提交速度。

  5. DiscardPolicy

  6. 如果队列已满,新提交的任务将被直接丢弃,不会抛出异常,也不会有任何其他处理。

  7. DiscardOldestPolicy

  8. 如果队列已满,线程池会丢弃最早排队的任务(即队列头部的任务),然后尝试重新将新任务加入到队列中。如果再次失败,则重复此过程。

示例讲解

Java 中的 ThreadPoolExecutor 提供了四种默认的拒绝策略,每种策略都实现了 RejectedExecutionHandler 接口。这四种策略分别是:

  1. AbortPolicy:默认策略,抛出 RejectedExecutionException

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

  3. DiscardPolicy:丢弃任务,不抛出异常。

  4. DiscardOldestPolicy:丢弃队列中最旧的未处理任务,然后重新尝试执行新任务。

以下是一个示例代码,展示了如何使用这四种拒绝策略:

import java.util.concurrent.*;

public class ThreadPoolRejectedPolicyExample {

    public static void main(String[] args) {
        // 创建一个线程池,核心线程数2,最大线程数4,队列容量2(为了触发拒绝策略)
        ThreadPoolExecutor executor = new ThreadPoolExecutor(
                2, 4, 60, TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2)
        );

        // 任务
        Runnable task = () -> {
            System.out.println(Thread.currentThread().getName() + " is executing task.");
            try {
                Thread.sleep(2000); // 模拟任务执行时间
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        };

        // 1. 使用 AbortPolicy(默认策略)
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
        try {
            for (int i = 0; i < 6; i++) {
                executor.execute(task);
            }
        } catch (RejectedExecutionException e) {
            System.out.println("Task rejected: " + e.getMessage());
        }

        // 重置线程池状态(实际应用中可能不会这样做,这里只是为了演示不同策略)
        executor.shutdown();
        executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));

        // 2. 使用 CallerRunsPolicy
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
        for (int i = 0; i < 6; i++) {
            executor.execute(task);
        }

        // 重置线程池状态
        executor.shutdown();
        executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));

        // 3. 使用 DiscardPolicy
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardPolicy());
        for (int i = 0; i < 6; i++) {
            executor.execute(task);
        }

        // 重置线程池状态
        executor.shutdown();
        executor = new ThreadPoolExecutor(2, 4, 60, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2));

        // 4. 使用 DiscardOldestPolicy
        executor.setRejectedExecutionHandler(new ThreadPoolExecutor.DiscardOldestPolicy());
        for (int i = 0; i < 6; i++) {
            executor.execute(task);
        }

        // 关闭线程池(最后一次使用后)
        executor.shutdown();
    }
}

代码讲解:

  1. 创建线程池

  2. 核心线程数设置为2,最大线程数设置为4,队列容量设置为2(ArrayBlockingQueue<>(2))。

    这样设置是为了容易触发拒绝策略,因为队列容量很小。

  3. 定义任务

  4. 创建一个简单的 Runnable 任务,该任务会打印执行线程的名称,并模拟执行2秒钟。

测试不同的拒绝策略

  1. 对于每种拒绝策略,我们设置线程池的拒绝处理器,并尝试提交6个任务。

    • 由于队列容量只有2,且最大线程数为4,因此当提交第5个任务时,就会触发拒绝策略。

    • AbortPolicy:会抛出 RejectedExecutionException,我们在 catch 块中捕获并打印消息。

    • CallerRunsPolicy:第5个及之后的任务将由主线程(调用 execute 的线程)执行。你会看到主线程也打印了执行任务的消息。

    • DiscardPolicy:第5个及之后的任务被丢弃,没有任何输出或异常。

    • DiscardOldestPolicy:队列中最旧的任务被丢弃,然后尝试重新提交新任务。由于队列始终保持两个任务,因此你会看到前两个提交的任务被接受,然后第5个任务替换了第1个任务(最旧的),第6个任务替换了第2个任务(因为此时第1个任务已经被替换并执行了)。实际输出可能因线程调度而异,但原则上是这样工作的。

相关文章
热点文章
精彩视频
Tags

站点地图 在线访客: 今日访问量: 昨日访问量: 总访问量: