线程池工厂接口
根据系统参数和实际中的任务并发需求,构造自定义参数,帮助更好了解一定业务下,机器适合的线程池参数阈值,同时调整适合自身业务的参数。
package com.hole.util;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
public interface ITheadPollFactory {
/**
* @describtion: 线程池创造工厂
* 支持满足不同的使用场景:根据不同的业务参数申请线程池参数
* 1、单次任务,连接数据库,io高,需要响应快,
* @param avgTasks 大部分情况下每秒的任务数量,每秒并发量
* @param taskTime 一个任务的平均处理时间(ms) todo:处理时间可能随着处理规模而增长
* @param taskName 对应业务指定线程名称
* @param systemMaxResponseTime 系统最大响应时间(ms)
* @param ioRatio 阻塞系数 = W / (W + C),即阻塞系数 = 阻塞时间 /(阻塞时间 + 计算时间) (0-0.9)
* @param rejectedExecutionHandler 拒绝策略
* @param customThreadFactory 线程工厂
* @author: guozi
* @create: 2021-03-12
*/
ThreadPoolExecutor createThreadPoolsByTaskDefinition(int avgTasks, int taskTime, String taskName, int systemMaxResponseTime,
double ioRatio, RejectedExecutionHandler rejectedExecutionHandler, ThreadFactory customThreadFactory) ;
}
工厂实现类
更具上述的参数生成线程池参数,这里默认是不建议任务有丢弃
package com.hole.util;
import com.hole.exception.WrongParameterRException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;
public class TreadPoolFactory implements ITheadPollFactory {
public static final Logger logger = LoggerFactory.getLogger(TreadPoolFactory.class);
//系统的cpu数量
//即线程等待时间所占比例越高,需要越多线程。线程CPU时间所占比例越高,需要越少线程。这就可以划分成两种任务类型:
//IO密集型 一般情况下,如果存在IO,那么肯定W/C > 1(阻塞耗时一般都是计算耗时的很多倍),但是需要考虑系统内存有限(每开启一个线程都需要内存空间),
// 这里需要在服务器上测试具体多少个线程数适合(CPU占比、线程数、总耗时、内存消耗)。如果不想去测试,保守点取1即可,Nthreads = Ncpu x (1 + 1) = 2Ncpu。
//多线程适用场景一般是:存在相当比例的IO和网络操作。 对比redis 单线程可以有几十万qps
/**
* 阻塞系数 = W / (W + C),即阻塞系数 = 阻塞时间 /(阻塞时间 + 计算时间)
* 线程数 = Ncpu /(1 - 阻塞系数) 定义线程数和阻塞系数的关系
*当阻塞系数为0.9时,表明90%的时间花在io上(网络数据传输、数据库、文件下载)
* IO密集型 = 2Ncpu(可以测试后自己控制大小,2Ncpu一般没问题)(常出现于线程中:数据库数据交互、文件上传下载、网络数据传输等等)
* 计算密集型 = Ncpu(常出现于线程中:复杂算法)避免了线程上下文的切换带来的损耗
*/
private static final int THREAD_NUMBER = Runtime.getRuntime().availableProcessors() / 2 + 1;
@Override
public ThreadPoolExecutor createThreadPoolsByTaskDefinition( int avgTasks, int taskTime, String taskName, int systemMaxResponseTime, double ioRatio,
RejectedExecutionHandler rejectedExecutionHandler, ThreadFactory customThreadFactory) {
if(ioRatio == 1 || taskTime == 0){
throw new WrongParameterRException("非法参数");
}
int avgPoolSize = (int) (avgTasks / (1 / ((double)taskTime / 1000))); //这个数很容易超过cpu上限数目 200~20(100ms) 200~40(200ms)
int maxThreadNums = (int) (THREAD_NUMBER * 2 / (1 - ioRatio)); //一般访问数据库io比都很低,给一个参照数,相当于几倍的机器线程
int maxThreadInExecutor = Integer.MAX_VALUE;
//当前阻塞和系统参数所设定的最大并发数,否则容易发生任务被丢弃
int maxTasks = (int) (maxThreadNums * (1 / ((double)taskTime / 1000)));
if(maxTasks < avgTasks){
logger.warn("当前设定并发数:{},系统最大并发数:{},此线程池较大可能发生任务丢弃,请修改参数", avgTasks, maxTasks);
}
//并发数大于实际线程数
if(maxThreadNums < avgPoolSize){ //cpu支持线程数小于最小并发所需线程数->增大线程池,增大队列,提高系统响应时间,尽量保证每个任务都有执行结果的情况下,指定拒绝策略
avgPoolSize = maxThreadNums;
}
int queueSize = (int) (avgPoolSize * (1 / ((double)taskTime / 1000)) * (systemMaxResponseTime /1000)); // 系统等待时间内利用poolsize能够解决的并发数
maxThreadInExecutor = maxThreadNums;
if(rejectedExecutionHandler == null ){
rejectedExecutionHandler = new CustomRejectedExecutionHandler();
}
if(customThreadFactory == null){
customThreadFactory = new CustomThreadFactory(taskName);
}
logger.info("线程池参数—--核心线程池大小:{},最大线程池数:{},队列容量:{},平均并发数:{}/s,系统容忍时间:{}ms,io阻塞比:{},系统cpu数:{},线程平均响应时间:{}",
avgPoolSize, maxThreadInExecutor, queueSize, avgTasks, systemMaxResponseTime, ioRatio, THREAD_NUMBER, taskTime);
return new ThreadPoolExecutor(avgPoolSize, maxThreadInExecutor, 30, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(queueSize),
customThreadFactory, rejectedExecutionHandler);
}
/**
* 制定自定义线程拒绝处理器类,记录异常和报警,默认的处理器,支持可以自定义加入
*/
private class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
public final Logger logger = LoggerFactory.getLogger(CustomRejectedExecutionHandler.class);
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
// 记录异常
logger.error("Task " + r.toString() + " rejected from " + executor.toString());
// 报警处理等,记录数据等
}
}
/**
* 定义创建线程工厂类,根据参数设定此次线程池的名称
*/
private class CustomThreadFactory implements ThreadFactory{
/**
* 此次线程池的任务名称
*/
private String taskName = "executor";
/**
* 线程计数
*/
private AtomicInteger count = new AtomicInteger(0);
CustomThreadFactory(String taskName){
this.taskName = taskName;
}
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
String threadName = "customExecutor_".toUpperCase() + taskName + count.addAndGet(1);
thread.setName(threadName);
return thread;
}
}
}
测试类
测试下来,在设定阈值的业务参数,不会发生任务丢弃,当然实际情况比这复杂,具体的情况一实际的业务需求为准。
package com.hole.util.threadPoolExecutor;
import com.hole.util.ITheadPollFactory;
import com.hole.util.TreadPoolFactory;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Queue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
public class ThreadPoolExecutor_TEST {
public static final Logger logger = LoggerFactory.getLogger(ThreadPoolExecutor_TEST.class);
public static void main(String[] args) throws Exception {
//ExecutorService threadPoolExecutor2 = Executors.newSingleThreadExecutor();
//测试线程池
ITheadPollFactory treadPoolFactory = new TreadPoolFactory();
ThreadPoolExecutor threadPoolExecutor = treadPoolFactory.createThreadPoolsByTaskDefinition(1000,
10, "test", 1000, 0.7, null, null);
final BlockingQueue<Runnable> queue = threadPoolExecutor.getQueue();
for (int i=0;i<1000;i++){
final int index = i;
try{
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
try {
SimpleDateFormat sdf = new SimpleDateFormat(
"HH:mm:ss");
logger.info("运行时间: " + sdf.format(new Date()) + " " + "任务序号:" + index + " " + "队列数量:" + queue.size()+ " " + "当前线程:" +Thread.currentThread());
Thread.sleep(70);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
}catch (Exception e){
logger.info("线程池异常:{}", e.getMessage());
}
}
threadPoolExecutor.shutdown();
}
}