16小节:基于动态代理模式完成线程池拒绝策略报警
作者:程序员马丁
热门项目实战社群,收获国内众多知名公司面试青睐,近千名同学面试成功!助力你在校招或社招上拿个offer。
基于动态代理模式完成线程池拒绝策略报警,元数据信息:
- 什么是线程池oneThread:https://t.zsxq.com/5GfrN
- 代码仓库:https://gitcode.net/nageoffer/onethread —— 申请项目权限参考上述线程池项目链接
- 章节难度:★★☆☆☆ - 中等
- 视频地址:本章节内容简单,无
©版权所有 - 拿个offer-开源&项目实战星球专属学习项目,依据《中华人民共和国著作权法实施条例》和《知识星球产权保护》,严禁未经本项目原作者明确书面授权擅自分享至 GitHub、Gitee 等任何开放平台。违者将面临法律追究。
内容摘要:本文介绍如何基于 JDK 动态代理和 Lambda 轻量级静态代理,优雅地扩展线程池的拒绝策略行为,实现拒绝次数统计与准实时告警。
课程目录如下所示:
- 前言
- 线程池拒绝任务场景
- 代理 模式
- 动态代理
- Lambda 轻量级静态代理
- 文末总结
在阅读本章节前,大家先通过马哥公众号的一篇文章 MyBatis动态代理核心原理 学习动态代理,下面文章将不再赘述。
前言
线程池作为任务并发处理的核心组件,其稳定性直接影响系统整体吞吐与响应能力。而线程池中的拒绝策略,作为最后一道防线,往往代表了系统已出现短时瓶颈或配置不合理等问题。
为此,我们希望在拒绝任务发生的第一时间:
- 上报报警,告知系统维护人员;
- 记录关键指标,便于事后分析与扩容调优;
- ......
现实比较骨感,JDK 线程池仅能完成拒绝基础逻辑,无法满足系统要求。下文将介绍如何优雅地使用代理模式,从静态代理逐步优化到动态代理,实现线程池拒绝任务的统计与实时告警功能。
线程池拒绝任务场景
线程池拒绝任务有两个主要触发条件:
- 线程池状态非运行状态。
- 阻塞队列已满,且线程池线程数达到最大值。
final void reject(Runnable command) {
handler.rejectedExecution(command, this);
}
注意到上述方法为默认权限且被 final
修饰,因此不能直接扩展。
代理模式
尽管线程池的拒绝任务方法被设置为 final
且具有默认访问权限,导致我们无法继承或重写该方法,但我们仍可以通过 代理模式 实现扩展功能。
代理模式(Proxy Design Pattern)是一种在不修改原始类代码的前提下,通过引入代理对象对其行为进行增强的设计手段,非常适合用于功能增强、权限控制、延迟加载等场景。
接下来我们将逐步实现 基于代理模式的拒绝策略扩展,包括拒绝次数统计与告警触发两个核心能力。
1. 扩展线程池
静态代理模式需要创建多个具体实现类来增强原始拒绝策略,如下所示:
public class SupportThreadPoolExecutor extends ThreadPoolExecutor {
/** 拒绝次数统计 */
private final AtomicInteger rejectCount = new AtomicInteger();
public SupportThreadPoolExecutor(...) {
super(...);
}
/** 拒绝次数自增 */
public void incrementRejectCount() {
rejectCount.incrementAndGet();
}
/** 获取当前拒绝次数 */
public int getRejectCount() {
return rejectCount.get();
}
}
2. 扩展拒绝策略
接下来,我们通过定义一个通用的拒绝策略扩展接口,为后续具体策略提供统一的增强能力,包括:
-
拒绝次数统计;
-
报警通知触发。
代码如下所示:
public interface SupportRejectedExecutionHandler extends RejectedExecutionHandler {
/**
* 拒绝策略前置处理逻辑:统计与告警。
*/
default void beforeReject(ThreadPoolExecutor executor) {
if (executor instanceof SupportThreadPoolExecutor) {
SupportThreadPoolExecutor supportExecutor = (SupportThreadPoolExecutor) executor;
// 拒绝次数自增
supportExecutor.incrementRejectCount();
// 执行告警逻辑(可替换为实际推送渠道)
System.out.println("线程池触发了任务拒绝...");
}
}
}
然后以 AbortPolicy
为例 ,实现一个具备扩展能力的拒绝策略类:
public class SupportAbortPolicyRejected extends ThreadPoolExecutor.AbortPolicy
implements SupportRejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
beforeReject(e); // 拒绝前执行扩展逻辑
super.rejectedExecution(r, e); // 调用原始策略行为
}
}
3. 功能验证
我们通过一个简单的测试用例,验证上述扩展拒绝策略是否能实现拒绝统计 + 告警输出的预期功能:
@SneakyThrows
public static void main(String[] args) {
SupportThreadPoolExecutor executor = new SupportThreadPoolExecutor(
1,
1,
1024,
TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1),
// 使用自定义的增强型拒绝策略
new SupportAbortPolicyRejected()
);
// 提交 3 个任务,超过最大线程数和队列容量,触发拒绝
for (int i = 0; i < 3; i++) {
try {
executor.execute(() -> Thread.sleep(Integer.MAX_VALUE));
} catch (Exception ex) {
// 忽略拒绝异常,专注验证统计与告警逻辑
}
}
Thread.sleep(50);
System.out.println(String.format("线程池拒绝次数统计 :: %d", executor.getRejectCount()));
}
// 控制台输出示例:
线程池触发了任务拒绝...
线程池拒绝次数统计 :: 1
从日志可以确认,我们的扩展逻辑已成功生效:
- 拒绝策略触发时,执行了
beforeReject()
中的统计与日志输出。 - 线程池准确记录了被拒绝任务的次数。
4. 模式小结
上述扩展方案采用的是一种经典的设计模式:静态代理。建议大家在继续阅读之前,先在本地运行一遍示例代码,通过实践加深理解。
完成运行后,我们总结出一张图,帮助大家更直观地理解静态代理的工作机制:
通过拒绝策略的实战演练,大家对静态代理的实现方式已经有了初步认识。但静态代理真的足够优雅吗?我们不妨冷静分析一下其局限性: