手写精简版动态线程池
前言
- 项目启动后,我们可能需要增加线程数或缩小线程数。一般情况下,需要重启服务才会生效。本篇文章介绍一种修改线程池后不用重启服务的办法。当然这也归功于JUC工具包自身的强大。
技术栈
- Nacos 2.1.0
监听配置变更,然后修改线程池配置。类似发布订阅的模式,这块用其他也行,比如Redis。
- Spring Boot 2.7.3
项目主框架,不必多说。
实现逻辑
- 监听Nacos变更,然后修改线程池配置。
- 比如修改
corePoolSize
,最后还是依靠ThreadPoolExecutor#setCorePoolSize
去修改。
public void setCorePoolSize(int corePoolSize) {
if (corePoolSize < 0)
throw new IllegalArgumentException();
// 与当前核心线程数差值
int delta = corePoolSize - this.corePoolSize;
this.corePoolSize = corePoolSize;
// 如果当前存在线程比设置的数值大
if (workerCountOf(ctl.get()) > corePoolSize)
// 中断一些核心线程
interruptIdleWorkers();
else if (delta > 0) { // 如果当前线程少了
// We don't really know how many new threads are "needed".
// As a heuristic, prestart enough new workers (up to new
// core size) to handle the current number of tasks in
// queue, but stop if queue becomes empty while doing so.
int k = Math.min(delta, workQueue.size());
// 添加核心线程
while (k-- > 0 && addWorker(null, true)) {
if (workQueue.isEmpty())
break;
}
}
}
代码
- 线程池对象
DynamicThreadPoolExecutor.java
package com.sample.threadpool;
import java.util.concurrent.ThreadPoolExecutor;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* @author Zhou Zhongqing
* @ClassName DynamicThreadPoolProperties
* @description: 封装线程池
* @date 2022-09-25 15:09
*/
public class DynamicThreadPoolExecutor extends ThreadPoolTaskExecutor {
// 队列最大长度
private int queueCapacity = 1000;
// 线程池维护线程所允许的空闲时间
private int keepAliveSeconds = 300;
public DynamicThreadPoolExecutor(int corePoolSize, int maxPoolSize, String threadNamePrefix) {
this.setMaxPoolSize(maxPoolSize);
this.setCorePoolSize(corePoolSize);
this.setQueueCapacity(queueCapacity);
this.setKeepAliveSeconds(keepAliveSeconds);
// 线程池对拒绝任务(无线程可用)的处理策略
this.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
//线程名称前缀
this.setThreadNamePrefix(threadNamePrefix + "-");
}
}
- 配置内容的对象
DynamicThreadPoolProperties.java
package com.sample.properties;
import com.alibaba.nacos.api.config.convert.NacosConfigConverter;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.boot.context.properties.source.ConfigurationPropertySource;
import org.springframework.boot.context.properties.source.MapConfigurationPropertySource;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.ByteArrayResource;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.Properties;
/**
* @author Zhou Zhongqing
* @ClassName DynamicThreadPoolProperties
* @description: 配置内容的对象
* @date 2022-09-25 15:09
*/
public class DynamicThreadPoolProperties implements NacosConfigConverter {
private List<ExecutorProperties> executors;
@Override
public String toString() {
return "DynamicThreadPoolProperties{" +
"executors=" + executors +
'}';
}
public List<ExecutorProperties> getExecutors() {
return executors;
}
public void setExecutors(List<ExecutorProperties> executors) {
this.executors = executors;
}
@Override
public boolean canConvert(Class targetType) {
return true;
}
// 转换字符串配置为对象
@Override
public Object convert(String config) {
// 读取配置并转换成对象
YamlPropertiesFactoryBean yaml = new YamlPropertiesFactoryBean();
yaml.setResources(new ByteArrayResource(config.getBytes(StandardCharsets.UTF_8)));
Properties properties = yaml.getObject();
DynamicThreadPoolProperties dynamicThreadPoolProperties = new DynamicThreadPoolProperties();
ConfigurationPropertySource source = new MapConfigurationPropertySource(properties);
Binder binder = new Binder(source);
ResolvableType resolvableType = ResolvableType.forClass(DynamicThreadPoolProperties.class);
Bindable<Object> objectBindable = Bindable.of(resolvableType).withExistingValue(dynamicThreadPoolProperties);
binder.bind("dynamic-thread-pool", objectBindable);
return dynamicThreadPoolProperties;
}
public static class ExecutorProperties {
private String name;
private Integer corePoolSize = 5;
private Integer maximumPoolSize = 10;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Integer getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(Integer corePoolSize) {
this.corePoolSize = corePoolSize;
}
public Integer getMaximumPoolSize() {
return maximumPoolSize;
}
public void setMaximumPoolSize(Integer maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
}
@Override
public String toString() {
return "ExecutorProperties{" +
"name='" + name + '\'' +
", corePoolSize=" + corePoolSize +
", maximumPoolSize=" + maximumPoolSize +
'}';
}
}
}
- 监听Nacos配置变化
NacosListener.java
package com.sample.listener;
import com.alibaba.nacos.api.config.annotation.NacosConfigListener;
import com.sample.properties.DynamicThreadPoolProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
/**
* @author Zhou Zhongqing
* @ClassName NacosListener
* @description: 监听Nacos配置变化
* @date 2022-09-25 15:09
*/
@Component
public class NacosListener {
@Resource
private ApplicationContext applicationContext;
@NacosConfigListener(dataId = "dynamic-thread-pool.yaml", converter = DynamicThreadPoolProperties.class)
public void onReceived(DynamicThreadPoolProperties properties) {
System.out.println("onReceived " + properties);
// 循环设置线程池的核心线程数和最大线程数
List<DynamicThreadPoolProperties.ExecutorProperties> executorProperties = properties.getExecutors();
for (DynamicThreadPoolProperties.ExecutorProperties executorProperty : executorProperties) {
ThreadPoolTaskExecutor threadPoolTaskExecutor = applicationContext.getBean(executorProperty.getName(), ThreadPoolTaskExecutor.class);
System.out.println(executorProperty.getName() + " 重新配置 核心线程数: " + executorProperty.getCorePoolSize() + " 最大线程数 :" + executorProperty.getMaximumPoolSize());
threadPoolTaskExecutor.setCorePoolSize(executorProperty.getCorePoolSize());
threadPoolTaskExecutor.setMaxPoolSize(executorProperty.getMaximumPoolSize());
}
}
}
- 注册线程池对象
RegisterThreadPoolBean.java
。这里利用了Spring的扩展点BeanDefinitionRegistryPostProcessor
,如果不清楚BeanDefinitionRegistryPostProcessor
可以参考本人拙作spring扩展 BeanDefinitionRegistryPostProcessor详解。
这个功能用ImportBeanDefinitionRegistrar接口也是可以的,然后用@Import导入。可以参考本人拙作 https://sample.blog.csdn.net/article/details/90613661
package com.sample.threadpool;
import com.sample.properties.DynamicThreadPoolProperties;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.BeanDefinitionRegistryPostProcessor;
import org.springframework.beans.factory.support.GenericBeanDefinition;
import org.springframework.boot.context.properties.bind.Bindable;
import org.springframework.boot.context.properties.bind.Binder;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.ResolvableType;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import java.util.List;
/**
* @author Zhou Zhongqing
* @ClassName RegisterThreadPool
* @description: 注册线程池注册到Spring。
* 这个功能用ImportBeanDefinitionRegistrar接口也是可以的,然后用@Import导入。可以参考本人拙作 https://sample.blog.csdn.net/article/details/90613661
* @date 2022-09-25 15:09
*/
@Component
public class RegisterThreadPoolBean implements BeanDefinitionRegistryPostProcessor, EnvironmentAware {
private Environment environment;
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry beanDefinitionRegistry) throws BeansException {
// 读取配置
DynamicThreadPoolProperties dynamicThreadPoolProperties = new DynamicThreadPoolProperties();
Binder binder = Binder.get(environment);
ResolvableType resolvableType = ResolvableType.forClass(DynamicThreadPoolProperties.class);
Bindable<Object> objectBindable = Bindable.of(resolvableType).withExistingValue(dynamicThreadPoolProperties);
binder.bind("dynamic-thread-pool", objectBindable);
List<DynamicThreadPoolProperties.ExecutorProperties> executorProperties = dynamicThreadPoolProperties.getExecutors();
for (DynamicThreadPoolProperties.ExecutorProperties executorProperty : executorProperties) {
GenericBeanDefinition genericBeanDefinition = new GenericBeanDefinition();
genericBeanDefinition.setBeanClass(DynamicThreadPoolExecutor.class);
// 核心线程数
genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(executorProperty.getCorePoolSize());
// 最大线程数
genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(executorProperty.getMaximumPoolSize());
// 线程名称前缀
genericBeanDefinition.getConstructorArgumentValues().addGenericArgumentValue(executorProperty.getName());
// 注册Bean
beanDefinitionRegistry.registerBeanDefinition(executorProperty.getName(), genericBeanDefinition);
System.out.println("注册 " + executorProperty.getName() + " 线程池");
}
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory) throws BeansException {
}
@Override
public void setEnvironment(Environment environment) {
this.environment = environment;
}
}
- 测试接口
TestController.java
package com.sample.controller;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* @author Zhou Zhongqing
* @ClassName TestController
* @description: 测试
* @date 2022-09-25 15:09
*/
@RestController
public class TestController {
@Resource
private ThreadPoolTaskExecutor orderThreadPool;
@Resource
private ThreadPoolTaskExecutor smsThreadPool;
@RequestMapping("/test")
public String test() {
StringBuilder sb = new StringBuilder();
orderThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
});
smsThreadPool.execute(() -> {
System.out.println(Thread.currentThread().getName() + " 执行任务");
});
sb.append("orderThreadPool ");
sb.append(" 核心线程数 " + orderThreadPool.getCorePoolSize());
sb.append(" 最大线程数 " + orderThreadPool.getMaxPoolSize());
sb.append("<br />");
sb.append("smsThreadPool ");
sb.append(" 核心线程数 " + smsThreadPool.getCorePoolSize());
sb.append(" 最大线程数 " + smsThreadPool.getMaxPoolSize());
return sb.toString();
}
}
- nacos配置
dynamic-thread-pool.yaml
dynamic-thread-pool:
executors:
- name: orderThreadPool
core-pool-size: 5
maximum-pool-size: 10
- name: smsThreadPool
core-pool-size: 5
maximum-pool-size: 10
效果
- 修改配置后,无需重启服务,再次请求接口。
小结
-
主要依赖JUC线程池自身强大,配置变更这块接个发布订阅的功能就可以了,如果没有用Nacos,利用Redis的发布订阅也是可以的。触发配置变更的通知。