当你提交任务到线程池(如Java中的ThreadPoolExecutor
)时,如果线程池的队列(工作队列,如LinkedBlockingQueue
)已满,这时会发生什么取决于你所使用的具体线程池配置及其拒绝策略(RejectedExecutionHandler
)。
Java中的ThreadPoolExecutor
提供了几种默认的拒绝策略:
AbortPolicy(默认策略):
如果队列已满并且线程池中的线程数达到了最大线程数,那么
ThreadPoolExecutor
会抛出RejectedExecutionException
,任务不会被执行。CallerRunsPolicy:
如果队列已满,任务将由调用
execute
方法的线程(即提交任务的线程)直接运行。这提供了一种降级的机制,可以降低新任务的提交速度。DiscardPolicy:
如果队列已满,新提交的任务将被直接丢弃,不会抛出异常,也不会有任何其他处理。
DiscardOldestPolicy:
如果队列已满,线程池会丢弃最早排队的任务(即队列头部的任务),然后尝试重新将新任务加入到队列中。如果再次失败,则重复此过程。
示例讲解
Java 中的 ThreadPoolExecutor
提供了四种默认的拒绝策略,每种策略都实现了 RejectedExecutionHandler
接口。这四种策略分别是:
AbortPolicy:默认策略,抛出
RejectedExecutionException
。CallerRunsPolicy:由调用线程(提交任务的线程)处理该任务。
DiscardPolicy:丢弃任务,不抛出异常。
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();
}
}
代码讲解:
创建线程池:
核心线程数设置为2,最大线程数设置为4,队列容量设置为2(
ArrayBlockingQueue<>(2)
)。这样设置是为了容易触发拒绝策略,因为队列容量很小。
定义任务:
创建一个简单的
Runnable
任务,该任务会打印执行线程的名称,并模拟执行2秒钟。
测试不同的拒绝策略:
对于每种拒绝策略,我们设置线程池的拒绝处理器,并尝试提交6个任务。
由于队列容量只有2,且最大线程数为4,因此当提交第5个任务时,就会触发拒绝策略。
AbortPolicy:会抛出
RejectedExecutionException
,我们在catch
块中捕获并打印消息。CallerRunsPolicy:第5个及之后的任务将由主线程(调用
execute
的线程)执行。你会看到主线程也打印了执行任务的消息。DiscardPolicy:第5个及之后的任务被丢弃,没有任何输出或异常。
DiscardOldestPolicy:队列中最旧的任务被丢弃,然后尝试重新提交新任务。由于队列始终保持两个任务,因此你会看到前两个提交的任务被接受,然后第5个任务替换了第1个任务(最旧的),第6个任务替换了第2个任务(因为此时第1个任务已经被替换并执行了)。实际输出可能因线程调度而异,但原则上是这样工作的。