1.前言
在高并发时代的今天,线程这个概念尤为重要,Java中创建一个线程的方式有很多,但是如今大部分公司都禁止手动创建线程来异步执行任务,而是必须使用线程池创建线程来执行任务,线程池可以帮助我们管理线程,与回收线程。大大降低消耗系统的资源与提高系统性能。
2.创建线程池
创建线程池的常见方法无非两种:
1.使用Executors工具类创建线程池,这个方法虽然方便但是缺点也很多,我实习的公司开发规范中命令禁止使用Executors工具类创建线程,至于为什么禁止使用我后续会发一篇详解线程池的文章其中就会提到这个问题
2.自己new ThreadPoolExecutor()使用其中的多个有参构造器可以很方便的帮助我们自定义创建线程池,每个参数都由我们自己配置,也可以帮助我们更了解我们创建的线程池性能指标。
3. 动态线程池
很早之前就听说美团开源了自己使用的动态线程池,前两天得空从GitHub上拉下来项目阅读了源码后,突发奇想写了一个简易版的动态线程池,虽然和美团开源的动态线程池没得比,但是还是很有学习意义的。
1.实现动态线程池的监控功能
这个功能的核心是使用动态代理,创建拒绝策略的动态代理对象,由于拒绝策略必须实现
RejectedExecutionHandler接口,所以我们可以直接使用JDK动态代理,使用Proxy.newInstance()方法创建拒绝策略的代理对象
要实现代理类,就需要我们写一个类实现InvocationHandler接口,其中来写我们的增强方法,即在代理类中在target对象执行方法前执行的代码,这里我就只实现了记录被拒绝次数,其实还可以监控很多数据。
2.对外提供构建线程池的类,在这个类中我们提供了几个静态方法供外界创建线程,创建了全局的Map变量用于存储所有通过这个类创建的线程池,为后续使用配置中心刷新线程池配置做准备。
当时写代码的时候我想到既然这个类中都存储了所有构建的线程池,那在这个类中用来实时监控线程池参数也就不需要之前的代理类了。所以我创建了一个定时线程池,定时去遍历存储所有线程池的Map集合,监控线程池中的任务使用情况,以及非核心线程池使用情况
这个RefrashThreadPoolConfig方法就是使用已经解析过的从配置中心发送来的新配置,来配置线程池
3.使用Nacos配置中心来动态配置线程池
1.项目导入Nacos-config依赖,注意依赖版本要和Springboot版本对应
创建bootStrap.yml编写配置
我打红线的配置都是很重要的配置,必须要配置,否则无法正确从配置中心读取配置,至于profiles.active这个配置不配置也无所谓,只要在nacos配置中心编写配置文件的时候中间不用加dev即可
在nacos中编写配置文件即可,这里不做过多赘述
最最最重要的一步来了 ,在项目中配置nacos配置监听器
这里使用了InitialzingBean接口,即spring提供的bean初始化回调接口,在spring初始化完成这个bean后,如果我们的组件实现了这个接口spring就会回调afterPropertisSet()这个方法来对我们的bean做增强
逻辑也很简单,大家自己看代码即可,我们在nacos动态刷新配置后,回调接口
@Component
public class DynamicThreadPoolRefresher implements InitializingBean {
//实现InitializingBean接口在初始化类后会回调afterPropertis方法来增强bean
@Resource
private NacosConfigManager nacosConfigManager;
@Override
public void afterPropertiesSet() throws Exception {
nacosConfigManager.getConfigService().addListener("threadpool-dev.properties", "DEFAULT_GROUP", new Listener() {
@Override
public Executor getExecutor() {
//返回一个线程池
return Executors.newSingleThreadExecutor();
}
@Override
public void receiveConfigInfo(String s) {
refreshConfig(s);
}
});
}
private void refreshConfig(String config) {
Properties threadProperties = new Properties();
try {
threadProperties.load(new StringReader(config));
} catch (IOException e) {
throw new RuntimeException(e);
}
Set<Object> configSet = threadProperties.keySet();
Map<String,Integer> configMap = new HashMap<>();
for (Object o : configSet) {
String configString = o.toString();
String[] configNums = configString.split("\\.");
//线程池名字
String threadPoolName = configNums[2];
//配置参数类型
String configType = configNums[3];
//将线程池名字与参数类型作为key,可以防止配置到其他线程池中去
String configKey = threadPoolName+configType;
String configPropertiesValue = threadProperties.getProperty(o.toString());
configMap.put(configKey, Integer.valueOf(configPropertiesValue));
System.out.println("修改线程池参数:" + configKey + "为" + configPropertiesValue);
}
DynamicThreadRegister.refreshThreadPoolConfig(configMap);
}
}
效果演示:
这里我写了一个测试类,实现ApplicationRunner接口,可以帮助我们进行效果演示