Skip to main content

16小节:基于动态代理模式完成线程池拒绝策略报警

作者:程序员马丁

在线博客:https://nageoffer.com

note

热门项目实战社群,收获国内众多知名公司面试青睐,近千名同学面试成功!助力你在校招或社招上拿个offer。

基于动态代理模式完成线程池拒绝策略报警,元数据信息:

©版权所有 - 拿个offer-开源&项目实战星球专属学习项目,依据《中华人民共和国著作权法实施条例》《知识星球产权保护》,严禁未经本项目原作者明确书面授权擅自分享至 GitHub、Gitee 等任何开放平台。违者将面临法律追究。


内容摘要:本文介绍如何基于 JDK 动态代理和 Lambda 轻量级静态代理,优雅地扩展线程池的拒绝策略行为,实现拒绝次数统计与准实时告警。

课程目录如下所示:

  • 前言
  • 线程池拒绝任务场景
  • 代理模式
  • 动态代理
  • Lambda 轻量级静态代理
  • 文末总结

在阅读本章节前,大家先通过马哥公众号的一篇文章 MyBatis动态代理核心原理 学习动态代理,下面文章将不再赘述。

前言

线程池作为任务并发处理的核心组件,其稳定性直接影响系统整体吞吐与响应能力。而线程池中的拒绝策略,作为最后一道防线,往往代表了系统已出现短时瓶颈或配置不合理等问题。

为此,我们希望在拒绝任务发生的第一时间:

  • 上报报警,告知系统维护人员;
  • 记录关键指标,便于事后分析与扩容调优;
  • ......

现实比较骨感,JDK 线程池仅能完成拒绝基础逻辑,无法满足系统要求。下文将介绍如何优雅地使用代理模式,从静态代理逐步优化到动态代理,实现线程池拒绝任务的统计与实时告警功能。

线程池拒绝任务场景

线程池拒绝任务有两个主要触发条件:

  1. 线程池状态非运行状态。
  2. 阻塞队列已满,且线程池线程数达到最大值。
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. 模式小结

上述扩展方案采用的是一种经典的设计模式:静态代理。建议大家在继续阅读之前,先在本地运行一遍示例代码,通过实践加深理解。

完成运行后,我们总结出一张图,帮助大家更直观地理解静态代理的工作机制

通过拒绝策略的实战演练,大家对静态代理的实现方式已经有了初步认识。但静态代理真的足够优雅吗?我们不妨冷静分析一下其局限性:

解锁付费内容,👉 戳