前言:在上篇文章我已经给读者介绍了Java线程池的基本使用,以及参数的定义。你真的了解Java线程池参数的含义吗
本文我们更进一步,来聊聊在实际的工作中如何设置Java线程池参数的。
当我们自定义线程池的时候 corePoolSize、maximumPoolSize、workQueue(队列长度)该如何设置?
你以为我要给你讲分 IO 密集型任务或者分 CPU 密集型任务?
当然这个教科书式的流程我们决不能少!
1:IO密集型任务时:
《Java并发编程实战》一书中给出的计算方式是这样的:
2:CPU密集型任务:
可以把核心线程数设置为核心数+1。
为什么要加一呢?
《Java并发编程实战》一书中给出的原因是:即使当计算(CPU)密集型的线程偶尔由于页缺失故障或者其他原因而暂停时,这个“额外”的线程也能确保 CPU 的时钟周期不会被浪费。
看不懂是不是?没关系我也看不懂。反正把它理解为一个备份的线程就行了。
教科书的痛点:
我之前有个系统就是按照这个公式算出来的参数去配置的。
结果效果并不好,甚至让下游系统直呼受不了。
这个东西怎么说呢,还是得记住,面试的时候有用。真实场景中只能得到一个参考值,基于这个参考值,再去进行调整。
我们再看一下美团的那篇文章调研的现有解决方案列表:
第一个就是我们上面说的,和实际业务场景有所偏离。
第二个设置为 2*CPU 核心数,有点像是把任务都当做 IO 密集型去处理了。而且一个项目里面一般来说不止一个自定义线程池吧?比如有专门处理数据上送的线程池,有专门处理查询请求的线程池,这样去做一个简单的线程隔离。但是如果都用这样的参数配置的话,显然是不合理的。
第三个不说了,理想状态。流量是不可能这么均衡的,就拿美团来说,下午3,4点的流量,能和 12 点左右午饭时的流量比吗?
基于上面的这些解决方案的痛点,美团给出了动态化配置的解决方案。
动态更新的工作原理是什么?
在运行期线程池使用方调用此方法设置corePoolSize之后,线程池会直接覆盖原来的corePoolSize值,并且基于当前值和原始值的比较结果采取不同的处理策略。
对于当前值小于当前工作线程数的情况,说明有多余的worker线程,此时会向当前idle的worker线程发起中断请求以实现回收,多余的worker在下次idel的时候也会被回收;
对于当前值大于原始值且当前队列中有待执行任务,则线程池会创建新的worker线程来执行队列任务,setCorePoolSize具体流程
因此动态更新线程参数的核心在于:
setCorePoolSize setMaxNumPoolSize 以及重新设置队列长度三个方法。
由于美团的文章发表只是理论上的概念并未发布源码。
因此参考美团文章给出的思路我来尝试实现微服务的动态更新线程池参数的Stater.
1:新建一个动态调整线程池参数的Stater,命名为 iread-threadfactory
2: 由于需要调整最大线程数、核心线程数、队列长度三个参数,因此将三个参数做成可配置的,又因为需要辨别每个线程,因此还需要设置线程池的名字。因此建立如下配置类:
WoreadThreadFactoryProperties
package com.cn.woread.configuration;
import java.util.ArrayList;
import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ConfigurationProperties(prefix = "woread.thread")
public class WoreadThreadFactoryProperties {
Listlist=new ArrayList();
public List getList() {
return list;
}
public void setList(List list) {
this.list = list;
}
}
Properties
package com.cn.woread.configuration;
public class Properties {
private String threadFactoryName;
//最大线程数
private int maximumPoolSize;
//核心线程数
private int corePoolSize;
//队列大小
private int capacity;
public String getThreadFactoryName() {
return threadFactoryName;
}
public void setThreadFactoryName(String threadFactoryName) {
this.threadFactoryName = threadFactoryName;
}
public int getMaximumPoolSize() {
return maximumPoolSize;
}
public void setMaximumPoolSize(int maximumPoolSize) {
this.maximumPoolSize = maximumPoolSize;
}
public int getCorePoolSize() {
return corePoolSize;
}
public void setCorePoolSize(int corePoolSize) {
this.corePoolSize = corePoolSize;
}
public int getCapacity() {
return capacity;
}
public void setCapacity(int capacity) {
this.capacity = capacity;
}
}
3:创建线程池创建处理类:WoreadThreadPoolExecture
package com.cn.woread.configuration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.springframework.beans.factory.annotation.Autowired;
public class WoreadThreadPoolExecture {
private WoreadThreadFactoryProperties properties;
private static Map threadFactorys=new HashMap();
public WoreadThreadPoolExecture(WoreadThreadFactoryProperties properties) {
this.properties=properties;
}
public ThreadPoolExecutor reBulidThreadFactory(String name) {
Listlist=properties.getList();
ThreadPoolExecutor threadFactory=null;
if(list!=null&&list.size()!=0) {
Optional mapOpt=list.stream().filter(l->name.equals(l.getThreadFactoryName())).findFirst();
int maximumPoolSize=Runtime.getRuntime().availableProcessors() +1;
int corePoolSize=maximumPoolSize;
int capacity=1000;
if(mapOpt.isPresent()) {
Properties map=mapOpt.get();
//最大线程数
maximumPoolSize=map.getMaximumPoolSize();
//核心线程数
corePoolSize=map.getCorePoolSize();
//队列大小
capacity=map.getCapacity();
}
if(threadFactorys.containsKey(name)) {
threadFactory=threadFactorys.get(name);
threadFactory.setCorePoolSize(corePoolSize);
threadFactory.setMaximumPoolSize(maximumPoolSize);
WoreadLinkedBlockingQueue queue=(WoreadLinkedBlockingQueue)threadFactory.getQueue();
queue.setCapacity(capacity);
threadFactory.prestartAllCoreThreads();
}else {
threadFactory=new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 60, TimeUnit.SECONDS, new WoreadLinkedBlockingQueue(capacity));
threadFactorys.put(name, threadFactory);
}
}
return threadFactory;
}
}
该类只有一个方法:ThreadPoolExecutor reBulidThreadFactory(String name)
根据线程池名字创建相应参数的线程池(如果从未创建)
根据线程池名字创建相应参数的线程池(如果已经创建---实现动态调整参数的需求)
private static Map
利用一个静态的map存储所有创建的线程池对象
1 threadFactory.setCorePoolSize(corePoolSize);
2 threadFactory.setMaximumPoolSize(maximumPoolSize);
3 WoreadLinkedBlockingQueue queue=(WoreadLinkedBlockingQueue)threadFactory.getQueue();
4 queue.setCapacity(capacity);
5 threadFactory.prestartAllCoreThreads();
1-2行代码利用配置文件配置的线程数量来重新设置线程参数,可是却未找到重新设置队列长度的方法,通过翻看源码发现,
队列长度capacity被设置成了final对象,不可更改,因此我的做法是重写队列,将大小设置为可改变的,提供改变方法
创建 线程队列类:WoreadLinkedBlockingQueue
4:创建staer类WoreadThreadFactoryConfiguration
package com.cn.woread.stater;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import com.cn.woread.configuration.WoreadThreadFactoryProperties;
import com.cn.woread.configuration.WoreadThreadPoolExecture;
@Configuration
@EnableConfigurationProperties(WoreadThreadFactoryProperties.class)
public class WoreadThreadFactoryConfiguration {
@Autowired
private WoreadThreadFactoryProperties properties;
@Bean
@ConditionalOnMissingBean(WoreadThreadPoolExecture.class)
public WoreadThreadPoolExecture woreadThreadPoolExecture() {
return new WoreadThreadPoolExecture(properties);
}
}
至此动态调整参数的线程池stater构建完毕。