拒绝策略的实现思路
自定义线程池:拒绝策略的实现思路
在手写线程池的过程中,拒绝策略是保障线程池稳定运行的关键兜底机制。它解决了任务过载时的处理问题,本文从设计初衷、核心思想到代码实现,完整梳理拒绝策略的设计与落地思路。
一、设计初衷:为什么需要拒绝策略?
线程池在运行时存在两个硬性限制:
- 核心线程数量固定,不能无限制创建新线程
- 阻塞队列容量有限,不能无限缓存待执行任务
当新任务提交时,若出现 核心线程已满 + 阻塞队列已满 的情况,任务既无法立即执行,也无法暂存队列。
为了避免程序阻塞、任务丢失或系统资源耗尽,必须提供一套灵活的兜底处理方案,这便是拒绝策略的设计意义。
二、核心设计思想
拒绝策略采用 策略模式 进行设计,遵循两个核心原则:
- 职责分离,解耦实现
- 线程池:只负责判断任务是否达到拒绝条件
- 阻塞队列:只负责判断自身容量是否已满
- 拒绝逻辑:由外部自定义,不硬编码在线程池内部
- 灵活可扩展
切换或新增拒绝策略时,无需修改线程池与队列源码,只需替换外部实现逻辑即可。
这套设计思路,与 JDK 原生 ThreadPoolExecutor 的拒绝策略设计完全一致,理解后可以无缝对接原生线程池学习。
三、分步实现思路
1. 定义函数式接口:统一拒绝策略规范
先通过接口约定拒绝行为,保证所有拒绝策略都有统一的调用方式。
同时使用函数式接口,支持直接使用 Lambda 表达式实现策略。
@FunctionalInterface
interface RejectPolicy<T> {
/**
* 拒绝策略核心方法
* @param queue 阻塞队列,可用于再次尝试入队
* @param task 被拒绝的任务
*/
void reject(BlockingQueue<T> queue, T task);
}
2. 线程池持有拒绝策略
拒绝策略属于线程池配置,由外部传入,线程池只负责保存与传递,不关心具体实现。
class ThreadPool {
// 拒绝策略对象
private RejectPolicy<Runnable> rejectPolicy;
public ThreadPool(int coreSize, long timeout, TimeUnit timeUnit,
int queueCapacity, RejectPolicy<Runnable> rejectPolicy) {
this.coreSize = coreSize;
this.timeout = timeout;
this.timeUnit = timeUnit;
this.taskQueue = new BlockingQueue<>(queueCapacity);
// 注入外部自定义拒绝策略
this.rejectPolicy = rejectPolicy;
}
}
3. 任务提交:判断是否触发拒绝流程
在线程池执行任务的入口方法中,对任务进行分流:
- 工作线程数未达到核心线程数:直接新建线程执行任务
- 工作线程数已满:尝试将任务加入阻塞队列
- 队列也满:由队列触发拒绝策略
public void execute(Runnable task) {
synchronized (workers) {
// 线程未满:直接创建工作线程
if (workers.size() < coreSize) {
Worker worker = new Worker(task);
workers.add(worker);
worker.start();
}
// 线程已满:尝试入队,队列满则执行拒绝策略
else {
taskQueue.tryPut(rejectPolicy, task);
}
}
}
4. 阻塞队列:最终判断并执行拒绝
队列自身最清楚容量状态,在 tryPut 方法中完成最终判断:
队列未满 → 正常添加任务;
队列已满 → 执行外部传入的拒绝策略。
public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
lock.lock();
try {
if (queue.size() == capacity) {
// 队列已满,执行拒绝策略
rejectPolicy.reject(this, task);
} else {
// 正常入队,并唤醒等待的工作线程
queue.addLast(task);
emptyWaitSet.signal();
}
} finally {
lock.unlock();
}
}
5. 外部自定义拒绝策略
使用线程池时,通过 Lambda 灵活定义拒绝行为,常见策略包括:
- 无限阻塞等待入队
- 带超时时间等待入队
- 直接丢弃任务
- 抛出异常通知上层
- 由提交任务的线程自己执行
ThreadPool threadPool = new ThreadPool(1, 1000, TimeUnit.MILLISECONDS, 1,
(queue, task) -> {
// 自定义拒绝逻辑:调用者线程自行执行
task.run();
});
四、通用任务执行分流逻辑
抛开具体代码参数,拒绝策略的通用执行流程可以总结为:
- 新任务提交到线程池
- 判断当前工作线程数是否小于核心线程数
- 是:创建新线程执行任务
- 否:尝试将任务加入阻塞队列
- 队列加入成功:等待工作线程消费
- 队列加入失败(队列已满):触发拒绝策略
- 执行自定义兜底逻辑(丢弃、抛异常、调用者执行等)
五、设计优势
- 解耦彻底
线程池、阻塞队列、拒绝逻辑三者相互独立,互不侵入,便于维护和扩展。 - 扩展性强
新增拒绝策略只需新增实现,无需改动线程池核心代码。 - 灵活性高
支持多种拒绝行为,可根据业务场景自由选择。 - 贴近工业级实现
设计思想对齐 JDK 原生线程池,学习成本低、实用性强。
六、总结
拒绝策略的本质,是线程池面对任务过载时的兜底保护机制。
通过策略模式将“是否拒绝”与“如何拒绝”分离,既保证了线程池的稳定性,又保留了极高的灵活性。
理解这一设计思路,不仅能看懂手写线程池,也能更深入掌握 Java 原生线程池的工作原理。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来源 Bird的个人博客!





