为什么会有线程池?
如果客户端发一个请求,服务端就创建一个线程接收请求,线程资源是有限的,而且创建一个线程和执行结束之后都要调用操作系统资源销毁线程,这样频繁操作肯定非常占用cpu和内存资源,线程池的作用就是实现线程的资源复用和控制最大线程数,还有队列异步处理请求,这样就减少线程资源的浪费
一、ThreadPoolExecutor参数如何设置
核心线程数(corePoolSize)、最大线程数(maximumPoolSize)
可以根据三个方面进行分析:
- CPU密集型任务:要避免线程的上下文切换,所以尽量要少创建线程,把corePoolSize设置成CPU核心数+1(加1是为了避免当中有的线程因为特殊情况被挂起,为了尽可能的利用CPU资源)
- IO密集型任务:当线程操作IO资源时,CPU资源是闲置的,我们可以尽可能创建合适的线程数来压榨CPU资源,IO时间越长,可以设置更多的线程数,但是不一定越多越好,我们可以通过这个公式来计算:线程数=CPU核心数*(1+线程等待时间/线程运行总时间)
- 混合型任务
总时间-总时间(CPU)=线程等待时间
理想状态下可这样估算,但是系统可能运行有其它线程,所以要进行压测
jmeter压测:
pom.xml文件配置:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.5.0</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
java代码:
@SpringBootApplication
public class Application {
public static void main(String[] args){
SpringApplication.run(Application.class, args);
}
}
@RestController
public class TestController {
@GetMapping("/test")
public String test() throws InterruptedException {
Thread.sleep(1000);
return "test";
}
}
jmeter压测操作:
添加聚合报告
添加结果树视图
聚合结果报告
默认最大200个连接,总执行时间25s,平均响应时间(Average)4.2s,最小响应时间(Min)1s,最大响应时间(Max)8s,请求错误率(Error)0%,吞吐量(Throughput)每秒钟完成125.5个请求
设置最大800个连接,总执行时间9s,平均响应时间(Average)1.2s,最小响应时间(Min)1s,最大响应时间(Max)2.5s,请求错误率(Error)0%,吞吐量(Throughput)每秒钟完成560.6个请求
基本上压测最大连接数值如果平均响应时间和执行总时间趋于稳定为准
总结:
- CPU密集型任务:CPU核心数+1
- IO密集型任务:抽样系统高峰期CPU执行时间,再先通过公式算出理论值,然后通过理论值进行压测
- 压测的业务如果是核心业务,并发量高,比如1s需要处理1000个请求,corePoolSize和maximumPoolSize都可以设置成500,如果是非核心业务,并发量小,核心线程数保留10个就行,最大线程数设置1000
- 队列大小需要根据线程1s能处理多少请求,比如1s能处理100个请求,队列中有300个任务,也就是说任务可能要等3s才能处理完,如果不能接受这个时间就要限制队列大小
二、ThreadPoolExecutor具体执行过程
1、JUC包下ThreadPoolExecutor执行流程
核心线程来一个任务创建一个线程,提交任务数超过核心线程数会放到队列中,队列中放不下会在小于maximumPoolSize前提又创建新任务,核心线程和非核心线程任务都做完了,都会到队列中去拿任务
2、tomcat的ThreadPoolExecutor执行流程
创建ThreadPoolExecutor对象时就已经初始化好了核心线程
三、ThreadPoolExecutor如何关闭
正常关闭、异常关闭、手动调用shutdown()、shutdownNow()方法
https://www.processon.com/view/link/64cb797372e1a4480965cf86
为什么线程池执行任务时抛出异常会重新创建一个线程?
方便做线程异常处理,如果直接忽略导致无法处理异常,所以让它抛出异常终止,然后重启一个线程
ThreadPoolExecutor executor = new ThreadPoolExecutor(10, 100, 1000, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<Runnable>(100), new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
Thread thread = new Thread(r);
thread.setUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("拦截异常");
}
});
return thread;
}
});