我个人在学习的时候一直主张任何技术都是为了解决痛点而产生的,而近几年动态线程池技术的热切发展也无一例外。
我们首先来谈一下线程池这个概念,只要是稍微具备规模的应用,都会用到这个技术,由于硬件技术的突飞猛进,支持多并发也成为了每个业务系统必不可少的一点。
线程池介绍
线程池是一种基于池化思想管理线程的工具,使用线程池可以减少创建销毁线程的开销,避免线程过多导致系统资源耗尽。充分利用池内计算资源,等待分配并发执行任务,提高系统性能和响应能力。
在业务系统开发过程中,线程池有两个常见的应用场景,分别是:快速响应用户请求和快速处理批量任务。
线程池应用场景
1. 快速响应用户请求
以电商中的查询商品详情接口举例,从用户发起请求开始,想要获取到商品全部信息,可能会包括获取商品基本信息、库存信息、优惠券以及评论等多个查询逻辑,假设每个查询是 50ms,如果是串行化查询则需要 200ms,查询性能一般。
而如果说通过线程池的方式并行查询,那查询全部商品信息的时间就取决于多个流程中最慢的那一条。
假设优惠信息流程查询时间 80ms,其他流程查询时间 50ms,经过线程池并行优化后,商品详情接口响应时间就是 80ms,通过并行缩短了整体查询时间。
import java.util.concurrent.atomic.AtomicInteger;
public class ThreadPoolDemo {
public static void main(String[] args) {
// 1. 核心参数定义(模拟 IO 密集型任务配置)
int corePoolSize = 2; // 核心线程数
int maximumPoolSize = 5; // 最大线程数
long keepAliveTime = 10; // 非核心线程空闲存活时间
TimeUnit unit = TimeUnit.SECONDS; // 时间单位
// 2. 阻塞队列(有界队列,防止 OOM)
// 只有当核心线程满了,新任务才会进入队列
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(2);
// 3. 自定义线程工厂(非常重要,给线程起名,方便日志排查)
ThreadFactory threadFactory = new NamedThreadFactory("Order-Service");
// 4. 拒绝策略(当队列满了 且 线程数达到最大值时触发)
// 这里演示 CallerRunsPolicy:由提交任务的线程自己执行,这是一种通过“降低提交速度”来缓解压力的策略
RejectedExecutionHandler handler = new ThreadPoolExecutor.CallerRunsPolicy();
// 5. 手动创建线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
threadFactory,
handler
);
// 6. 模拟提交任务
// 场景分析:
// 任务 1,2 -> 立即由核心线程执行
// 任务 3,4 -> 核心满了,进入队列
// 任务 5,6,7 -> 队列满了,创建非核心线程执行(直到达到 max 5)
// 任务 8+ -> 达到最大线程数且队列已满,触发拒绝策略 (CallerRuns)
try {
for (int i = 1; i <= 10; i++) {
final int taskId = i;
executor.execute(() -> {
try {
System.out.println(Thread.currentThread().getName() + " \t正在处理任务: " + taskId);
TimeUnit.SECONDS.sleep(1); // 模拟业务耗时
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
} finally {
// 7. 优雅关闭
executor.shutdown();
}
}
/**
* 内部类:自定义线程工厂
* 作用:为线程设置有意义的名字,生产环境中排查死锁或 CPU 飙高时非常有用。
*/
static class NamedThreadFactory implements ThreadFactory {
private final AtomicInteger threadNumber = new AtomicInteger(1);
private final String namePrefix;
public NamedThreadFactory(String namePrefix) {
this.namePrefix = namePrefix + "-Thread-";
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r, namePrefix + threadNumber.getAndIncrement());
// 可以在这里设置守护线程、优先级等
if (t.isDaemon())
t.setDaemon(false);
if (t.getPriority() != Thread.NORM_PRIORITY)
t.setPriority(Thread.NORM_PRIORITY);
return t;
}
}
}
线程池固然好用,但它也有属于自己的痛点,而动态线程池是专门为了解决原生JDK的痛点而产生的。 我们先谈谈原生线程池的痛点:
- 线程池一旦设定即为固定,我们没办法动态的调整它的参数。
- 线程池随便定义,线程资源过多,造成服务器高负载。
- 线程池参数不易评估,随着业务的并发提升,业务面临出现故障的风险。
- 线程池任务堆积,任务执行超时或触发拒绝策略,影响既有业务正常运行。
而动态线程池完美的解决了这一系列痛点,在接着阅读下面的内容的时候,建议大家先看一下美团在2020年提出的动态线程池观念
动态线程池的核心理念可以高度概括为: “将线程池的‘黑盒’运行模式,转变为‘白盒’的可视、可管、可控状态,利用配置中心实现运行时的流量治理。”
1. 配置中心化与动态变更 (Dynamic Configuration)
这是最基础的理念。传统的线程池参数(核心线程数、最大线程数、队列容量)通常写死在代码或静态配置文件中,修改需要重启服务。
- 核心思想:配置即代码,且实时生效。
- 解决痛点:业务流量具有潮汐效应(如双11大促、突发热点),静态配置无法应对动态变化的流量。
- 实现方式:利用 Nacos、Apollo 等配置中心,监听配置变更,调用 JDK 原生 API(如
setCorePoolSize)或自定义方法(如你修改的ResizingLinkedBlockingQueue)来实时调整参数。
2. 全链路监控与可视化 (Observability)
原生线程池是一个“黑盒”,开发者通常只知道它在跑,但不知道跑得怎么样。
- 核心思想:看见即掌控。
- 关键指标:不仅要看当前活跃线程数,还要看队列积压情况、任务拒绝次数、任务平均耗时、最大耗时等。
- 价值:通过监控面板(Grafana 等),开发者可以直观地看到线程池的水位,判断是否需要扩容或优化业务逻辑。
3. 主动预警与告警 (Alerting)
光有监控是不够的,系统需要在问题爆发前通知开发者。
-
核心思想:被动运维转主动治理。
-
场景:
- 当队列积压超过 80% 时,发送告警(防止 OOM)。
- 当连续触发拒绝策略时,发送告警(防止业务丢单)。
- 当任务执行时间过长时,发送告警(检测死锁或慢查询)。
-
你的实现:在代码中通过
DyThreadBeanPostProcessor注册以及动态代理RejectedExecutionHandler来实现告警逻辑,正是这一理念的体现。
4. 优雅的故障隔离与降级 (Isolation & Degradation)
在微服务架构中,不同业务混用线程池会导致雪崩效应。
- 核心思想:资源隔离,互不影响。
- 做法:为核心业务(如订单创建)和非核心业务(如日志打印)分配不同的动态线程池。当非核心业务线程池被打满时,不会拖垮核心业务。
- 动态性体现:如果某个非核心业务突然占用大量资源,可以通过动态调整将其线程数降到最低,实现“熔断”效果,保护主系统。
总结一个形象的类比:
-
原生线程池就像是一个固定红绿灯路口。无论车多车少,红灯绿灯的时间是固定的。早高峰车流巨大时,不仅堵死,还可能瘫痪(OOM/拒绝策略),交警(开发者)只能看着干着急,或者把路封了重修(重启服务)。
-
动态线程池就像是一个智能交通指挥系统。
- 监控(Observability) :摄像头实时统计车流量。
- 动态调节(Dynamic) :车多了,自动延长绿灯时间(增加核心线程/队列)。
- 告警(Alerting) :路口彻底堵死时,立即通知指挥中心。
- 配置中心(Nacos) :指挥中心可以远程一键下发指令,调整所有路口的通行策略。
接下来我们来讲动态线程池的核心也就是动态两个字 :基于配置刷新
什么意思呢, 动态线程池之所以能实现动态,是因为Nacos等配置中心的支持,准确的来说就是当你注册了配置中心的监听器,它会在检查并且拦截配置类的更改,这时候我们就可以基于传入的配置类的信息来修改,这也是实现动态的核心。
我们可以在项目启动的时候动态的更改线程池的配置,而恰好原生线程池有配置的set方法支持我们实现这一步: 举例:
public void setKeepAliveTime(long time, TimeUnit unit) {
if (time < 0)
throw new IllegalArgumentException();
if (time == 0 && allowsCoreThreadTimeOut())
throw new IllegalArgumentException("Core threads must have nonzero keep alive times");
long keepAliveTime = unit.toNanos(time);
long delta = keepAliveTime - this.keepAliveTime;
this.keepAliveTime = keepAliveTime;
if (delta < 0)
interruptIdleWorkers();
}
但还有几个核心痛点需要解决:
首先我们肯定是需要调整阻塞队列的大小的 ,如果使用的是 LinkedBlockingQueue,可能会发现它的容量是固定的,根本不支持动态调整
LinkedBlockingQueue 阻塞队列的容量是 int 类型,如果不设置容量默认是 Integer.MAX_VALUE,设置容量后,因为字段类型是 final,是没有办法变更容量的
那就需要一个办法来解决这一点了,大家第一时间想到的肯定是基于反射拿到值再进行修改,可以是可以,但有几个局限点
1.高版本开启模块安全检查后,无法修改 final 字段;使用反射需要添加安全机制
2.即使你变小了容量,但容量变小后无法阻塞 原因是因为LinkedBlockingQueue内部的判断机制 具体如下面源码所示 它只会判断是不是与容量相等。假设当前队列已存入 8 个元素,容量为 10。我们通过反射将容量缩小为 5,期望此后入队操作会被阻塞。
3。LinkedBlockingQueue内部是有阻塞队列的,但你只是简单的更新值,不会唤醒被阻塞的线程
所以我们只能另辟蹊径:这里参考了 RabbitMQ 团队的设计方案
直接复制并修改LinkedBlockingQueue源码,做成可变容量版本
其逻辑大概如下:
public boolean offer(E o) {
if (o == null) {
throw new NullPointerException();
}
final AtomicInteger count = this.count;
if (count.get() >= capacity) {
return false;
}
int c = -1;
final ReentrantLock putLock = this.putLock;
putLock.lock();
try {
if (count.get() < capacity) {
insert(o);
c = count.getAndIncrement();
if (c + 1 < capacity) {
notFull.signal();
}
}
} finally {
putLock.unlock();
}
if (c == 0) {
signalNotEmpty();
}
return c >= 0;
}
@Override
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
try {
while (count.get() == 0) {
notEmpty.await();
}
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to a non-interrupted thread
throw ie;
}
x = extract();
c = count.getAndDecrement();
if (c > 1) {
notEmpty.signal();
}
} finally {
takeLock.unlock();
}
if (c >= capacity) {
signalNotFull();
}
return x;
}
@Override
public E poll(long timeout, TimeUnit unit) throws InterruptedException {
E x = null;
int c = -1;
long nanos = unit.toNanos(timeout);
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();
try {
for (; ; ) {
if (count.get() > 0) {
x = extract();
c = count.getAndDecrement();
if (c > 1) {
notEmpty.signal();
}
break;
}
if (nanos <= 0) {
return null;
}
try {
nanos = notEmpty.awaitNanos(nanos);
} catch (InterruptedException ie) {
notEmpty.signal(); // propagate to a non-interrupted thread
throw ie;
}
}
} finally {
takeLock.unlock();
}
if (c >= capacity) {
signalNotFull();
}
return x;
}
@Override
public E poll() {
final AtomicInteger count = this.count;
if (count.get() == 0) {
return null;
}
E x = null;
int c = -1;
final ReentrantLock takeLock = this.takeLock;
takeLock.lock();
try {
if (count.get() > 0) {
x = extract();
c = count.getAndDecrement();
if (c > 1) {
notEmpty.signal();
}
}
} finally {
takeLock.unlock();
}
if (c >= capacity) {
signalNotFull();
}
return x;
}
到这里我们成功完成了容量的热更新
但接下来还有一个问题在等着我们:因为我们需要动态的观测线程池的核心指标,但我们应该如何统计拒绝任务数 原生的四种拒绝策略都不支持我们完成这一点 所以我们需要对它们的功能做一个增强。
尽管线程池的拒绝任务方法被设置为 final 且具有默认访问权限,导致我们无法继承或重写该方法 ,但我们仍可以通过 代理模式 实现扩展功能。
代理模式 (Proxy Design Pattern)是一种在不修改原始类代码的前提下 ,通过引入代理对象对其行为进行增强的设计手段,非常适合用于功能增强、权限控制、延迟加载等场景
常见的代理技术分为静态代理和动态代理
首先来谈谈静态代理 : 静态代理模式需要创建多个具体实现类来增强原始拒绝策略
import java.util.concurrent.atomic.AtomicLong;
/**
* 动态线程池治理框架 - 拒绝策略增强 Demo
* 核心亮点:利用接口默认方法与继承机制,实现拒绝策略的可观测性
*/
public class ThreadPoolEnhancementDemo {
public static void main(String[] args) throws InterruptedException {
// 1. 初始化自定义线程池
SupportThreadPoolExecutor executor = new SupportThreadPoolExecutor(
1, // 核心线程数
1, // 最大线程数
0L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1), // 队列容量为 1
new SupportAbortPolicy() // 【关键】注入增强后的拒绝策略
);
// 2. 模拟高并发场景:提交 5 个任务
// 预期:Task-0 执行, Task-1 进队列, Task-2/3/4 触发拒绝
System.out.println("====== 开始提交任务 ======");
for (int i = 0; i < 5; i++) {
int taskId = i;
try {
executor.execute(() -> {
try {
// 模拟任务耗时,占住线程不释放
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
});
} catch (RejectedExecutionException e) {
// 捕获异常是为了演示程序不中断,实际生产中由业务决定是否捕获
System.err.println("主线程捕获:任务 " + taskId + " 被拒绝");
}
}
// 3. 验证统计结果
Thread.sleep(100); // 稍作等待确保计数更新
System.out.println("\n====== 监控数据 ======");
System.out.println("线程池历史拒绝次数: " + executor.getRejectCount());
// 关闭线程池
executor.shutdown();
}
// ==========================================
// 核心组件定义 (Inner Classes)
// ==========================================
/**
* 组件 1: 支持监控能力的自定义线程池
* 作用:维护业务维度的统计指标(如拒绝次数)
*/
static class SupportThreadPoolExecutor extends ThreadPoolExecutor {
private final AtomicLong rejectCount = new AtomicLong(0);
public SupportThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, RejectedExecutionHandler handler) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue, Executors.defaultThreadFactory(), handler);
}
// 供拒绝策略回调
public void incrementRejectCount() {
rejectCount.incrementAndGet();
}
public long getRejectCount() {
return rejectCount.get();
}
}
/**
* 组件 2: 增强型拒绝策略接口
* 作用:利用 default 方法抽离通用的“告警与统计”逻辑
*/
interface SupportRejectedExecutionHandler extends RejectedExecutionHandler {
/**
* 拒绝策略前置处理逻辑:统计与告警
*/
default void beforeReject(ThreadPoolExecutor executor) {
if (executor instanceof SupportThreadPoolExecutor) {
SupportThreadPoolExecutor supportExecutor = (SupportThreadPoolExecutor) executor;
// 1. 拒绝次数自增
supportExecutor.incrementRejectCount();
// 2. 模拟发送告警 (Log/DingTalk/Email)
System.out.println(">>> [告警] 线程池触发了任务拒绝策略!当前拒绝总数:" + supportExecutor.getRejectCount());
}
}
}
/**
* 组件 3: 具体的增强策略实现 (以 AbortPolicy 为例)
* 作用:继承原生策略,并在拒绝前植入切面逻辑
*/
static class SupportAbortPolicy extends ThreadPoolExecutor.AbortPolicy implements SupportRejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
// AOP 思维:前置增强
beforeReject(e);
// 执行原生逻辑 (抛出异常)
super.rejectedExecution(r, e);
}
}
}
但静态代理有它的局限性:
- 爆炸问题 :每一种拒绝策略都需要手动创建对应的代理类来实现增强逻辑。以 JDK 提供的四种默认策略为例,若都需要扩展,就得创建四个额外类,显然不符合开闭原则 ,也不利于维护。
- 侵入性较高,增加系统复杂度 :所有线程池都必须显式使用这些增强后的拒绝策略,一旦项目规模扩大或开发人员更替,容易遗漏代理逻辑或出现不一致实现,造成潜在的系统风险。
所以我个人在开发的时候采取了动态代理的模式:
这里使用jdk动态代理劫持接口 例子如下:
这里我们利用 java.lang.reflect.InvocationHandler 来拦截 rejectedExecution 方法。
public class DynamicProxyRejectDemo {
public static void main(String[] args) {
// 1. 准备一个原始的拒绝策略 (可以是 Abort, CallerRuns 等任意一种)
RejectedExecutionHandler originalPolicy = new ThreadPoolExecutor.AbortPolicy();
// 2. 【核心】创建代理对象,把原始策略包进去
RejectedExecutionHandler proxyPolicy = RejectProxyUtil.createProxy(originalPolicy);
// 3. 将代理策略放入线程池
ThreadPoolExecutor executor = new ThreadPoolExecutor(
1, 1, 0, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(1),
Executors.defaultThreadFactory(),
proxyPolicy // <--- 注意这里放的是代理对象
);
// 4. 测试触发拒绝
try {
// 提交 3 个任务 (1个核心执行, 1个进队列, 第3个触发拒绝)
for (int i = 0; i < 3; i++) {
executor.execute(() -> {
try { Thread.sleep(100); } catch (InterruptedException e) { }
});
}
} catch (RejectedExecutionException e) {
System.err.println("业务方捕获到拒绝异常: " + e.getMessage());
}
executor.shutdown();
}
/**
* 工具类:专门用于生成代理对象
*/
static class RejectProxyUtil {
public static RejectedExecutionHandler createProxy(RejectedExecutionHandler target) {
// 使用 JDK 动态代理生成一个新的 RejectedExecutionHandler 实例
return (RejectedExecutionHandler) Proxy.newProxyInstance(
target.getClass().getClassLoader(),
new Class[]{RejectedExecutionHandler.class}, // 代理的接口
new RejectInvocationHandler(target) // 具体的拦截逻辑
);
}
}
/**
* 核心逻辑:拦截器 (InvocationHandler)
*/
static class RejectInvocationHandler implements InvocationHandler {
private final RejectedExecutionHandler target;
private final AtomicLong rejectCount = new AtomicLong(0);
public RejectInvocationHandler(RejectedExecutionHandler target) {
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 只拦截 rejectedExecution 方法
if ("rejectedExecution".equals(method.getName())) {
// --- AOP 增强逻辑:前置告警 ---
long count = rejectCount.incrementAndGet();
ThreadPoolExecutor executor = (ThreadPoolExecutor) args[1];
System.out.println(String.format(
"🚨 [动态代理告警] 线程池触发拒绝! 累计次数: %d, 当前策略: %s, 活跃线程: %d",
count,
target.getClass().getSimpleName(),
executor.getActiveCount()
));
// 可以在这里调用钉钉/飞书发送逻辑
// -----------------------------
}
try {
// 反射调用原始对象的逻辑 (比如 AbortPolicy 会在这里抛异常)
return method.invoke(target, args);
} catch (InvocationTargetException e) {
// 【关键点】反射会把原始异常包装成 InvocationTargetException
// 我们必须把里面的原始异常 (如 RejectedExecutionException) 拆包抛出去
// 否则业务方捕获不到正确的异常类型
throw e.getCause();
}
}
}
}
到目前为止我们解决了动态线程池参数的两大难题。
有没有什么可以高效管理动态线程池方法呢 - 最好的办法是可以依赖于spring的Bean管理机制, 让我们所需要的动态线程池被注册成一个Bean交给spring容器管理,我们可以注册springboot的后置bean处理器, 然后在springboot启动的时候进行动态的检测,是否加上了我们自定义的注解, 如果有自定义的注解,则注册进我们进行动态线程池管理的容器 : 话不多说 - 代码如下:
- 我们使用并发安全的Map来当作容器用来保证线程安全 private static final Map<String, ThreadPoolExecutorHolder> HOLDER_MAP = new ConcurrentHashMap<>();
后置Bean处理器: public class DyThreadBeanPostProcessor implements BeanPostProcessor {
private final BootstrapConfigProperties properties;
@Override
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof DyThreadExecutor) {
DynamicThreadPool dynamicThreadPool;
try {
// 通过 IOC 容器扫描 Bean 是否存在动态线程池注解
dynamicThreadPool = ApplicationContextHolder.findAnnotationOnBean(beanName, DynamicThreadPool.class);
if (Objects.isNull(dynamicThreadPool)) {
return bean;
}
} catch (Exception ex) {
log.error("Failed to create dynamic thread pool in annotation mode.", ex);
return bean;
}
DyThreadExecutor dyThreadExecutor = (DyThreadExecutor) bean;
// 从配置中心读取动态线程池配置并对线程池进行赋值
ThreadPoolExecutorProperties executorProperties = properties.getExecutors()
.stream()
.filter(each -> Objects.equals(dyThreadExecutor.getThreadPoolId(), each.getThreadPoolId()))
.findFirst()
.orElseThrow(() -> new RuntimeException("The thread pool id does not exist in the configuration."));
overrideLocalThreadPoolConfig(executorProperties, dyThreadExecutor);
DyThreadRegistry.putHolder(dyThreadExecutor.getThreadPoolId(), dyThreadExecutor, executorProperties);
}
return bean;
}
}
然后实现自动装配 ,也就是经典三步走类似于把大象装进冰箱:
- 写需要被装配的逻辑
- 写配置类 把逻辑注册为Bean
- 在org.springframework.boot.autoconfigure.AutoConfiguration.imports 这个文件中写自己的配置类的全类名 这样Springboot 启动类在启动的时候可以自动扫描引入的依赖下的文件 把其装配进主应用容器 这也是现在框架常用的 实现了零配置注入 把这个操作交给了框架 组件开发者。
这样我们在主程序中被标记为动态线程池的Bean就会交给核心的Map管理,方便我们进行动态的刷新 实现可追溯 实现Nacos的监听器方法(你也可以使用其它配置中心):
public void registerListener() throws NacosException {
BootstrapConfigProperties.NacosConfig nacosConfig = properties.getNacos();
configService.addListener(
nacosConfig.getDataId(),
nacosConfig.getGroup(),
new Listener() {
@Override
public Executor getExecutor() {
return ThreadPoolExecutorBuilder.builder()
.corePoolSize(1)
.maximumPoolSize(1)
.keepAliveTime(9999L)
.workQueueType(BlockingQueueTypeEnum.SYNCHRONOUS_QUEUE)
.threadFactory("clod-nacos-refresher-thread_")
.rejectedHandler(new ThreadPoolExecutor.CallerRunsPolicy())
.build();
}
@Override
public void receiveConfigInfo(String configInfo) {
refreshThreadPoolProperties(configInfo);
}
});
这段代码就是注册了一个Nacos的监听器,当其检测到配置类发生变更的时候就会执行回调方法,我们可以在方法里执行我们想执行的。
可插拔机制: 大部分框架 组件都提供了可插拔机制 例如FeignClients的 @EnableFeignClients 注解
@SpringBootApplication
@MapperScan("com.nageoffer.shortlink.admin.dao.mapper")
@EnableFeignClients
@EnableDiscoveryClient
public class ShortLinkAdminApplication {
public static void main(String[] args) {
System.out.println();
SpringApplication.run(ShortLinkAdminApplication.class, args);
}
}
我们可以加入这个注解来启用FeignClients 当我们点击它的源码就会发现 其实他就是一个标记 利用@import注册进了一段逻辑:
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
@Documented
@Import({FeignClientsRegistrar.class})
public @interface EnableFeignClients {
String[] value() default {};
String[] basePackages() default {};
Class<?>[] basePackageClasses() default {};
Class<?>[] defaultConfiguration() default {};
Class<?>[] clients() default {};
}
也就是FeignClientsRegistrar.class这个类 这个类就是实现FeignClients的核心 如果对其有兴趣可以点进去查看
总的来说 可插拔机制的核心在于“按需加载”,而其实现方式是多种多样的,比如:通过配置文件中的开关(如指定前缀的 Key)、或自定义注解控制模块启用。
由于相关逻辑我们已经在stater文件配置过 所以我们可以不采用FeignClient的直接注入方法 直接使用spring提供的@ConditionalOnProperty 和 @ConditionalOnBean来实现我们的功能 顾名思义 这两个注解的作用是基于指定的配置文件或者类来实现条件注入 , 也就是加在@AutoConfigure下的 下面是代码可以参考一下:
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
/**
* 动态线程池自动装配核心入口
* <p>
* 设计思想:Marker 模式 + 条件注解,实现“可插拔”的中间件设计
*/
@Configuration
public class DYThreadAutoConfiguration {
// ==========================================
// 1. MarkerConfiguration: 开关的“哨兵”
// ==========================================
/**
* 只有当配置 DYthread.enable = true (或不配置,默认开启) 时,
* 这个 Configuration 才会生效,进而创建 Marker Bean。
*/
@Configuration
@ConditionalOnProperty(
prefix = "onethread",
name = "enable",
havingValue = "true",
matchIfMissing = true // 约定大于配置:默认是开启的
)
public static class MarkerConfiguration {
@Bean
public Marker dynamicThreadPoolMarkerBean() {
return new Marker();
}
/**
* 这是一个没有任何业务逻辑的空类。
* 它的唯一作用就是作为一个“标记”:
* 如果 Spring 容器里有这个 Bean,说明用户开启了功能;否则说明用户关闭了。
*/
public class Marker {
}
}
// ==========================================
// 2. CommonAutoConfiguration: 真正的业务配置
// ==========================================
/**
* 核心业务配置类
* 只有当 Marker Bean 存在时,才加载下面的所有 Bean。
*/
@Configuration
@ConditionalOnBean(MarkerConfiguration.Marker.class) // 【核心】依赖 Marker 的存在
@Import(OneThreadBaseConfiguration.class) // 导入基础配置
@AutoConfigureAfter(OneThreadBaseConfiguration.class) // 控制加载顺序
public static class CommonAutoConfiguration {
// 这里定义你的核心 Bean,比如 Monitor, Service 等
// @Bean
// public DynamicThreadPoolService dynamicThreadPoolService() { ... }
// 只有当 onethread.enable=true -> MarkerCreated -> CommonAutoConfig 生效
// 这样的链路保证了系统的稳健性。
}
}
这样我们就可以实现可插拔机制 到现在为止这个动态线程池终于初具规模 大家可以自己写一个Demo来验证
接下来我们就要实现相关报警逻辑以及可观测体系构建 这些内容放入(2)
由于个人是是刚开始写相关技术博客,大部分内容都是自己手写生成,有些语句可能描述不当,希望可以包容一下。
回到文章开头所说,“任何技术都是为了解决痛点而产生的”。动态线程池技术的出现,正是为了打破原生 JDK 线程池“黑盒运行”和“静态配置”的僵局。
通过本文,我们从零开始构建了一个动态线程池治理框架:利用 Nacos 实现了参数的毫秒级热更新,通过重写阻塞队列打破了容量限制,借助 动态代理 赋予了拒绝策略以监控告警能力,最后利用 Spring Boot Starter 机制实现了“开箱即用”的丝滑体验。
希望通过这个从 0 到 1 的造轮子过程,能让你对 Java 并发编程、反射机制以及 Spring 扩展原理有更深的理解。虽然这是我技术写作的起步,但技术探索的道路永无止境,欢迎大家在评论区交流指正。