Skip to content

Commit c163bf3

Browse files
committed
线程池参数优化
1 parent 8236531 commit c163bf3

File tree

16 files changed

+2083
-0
lines changed

16 files changed

+2083
-0
lines changed

SpringBoot/ThreadPool/.gitignore

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
HELP.md
2+
target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
!**/src/main/**/target/
5+
!**/src/test/**/target/
6+
7+
### STS ###
8+
.apt_generated
9+
.classpath
10+
.factorypath
11+
.project
12+
.settings
13+
.springBeans
14+
.sts4-cache
15+
16+
### IntelliJ IDEA ###
17+
.idea
18+
*.iws
19+
*.iml
20+
*.ipr
21+
22+
### NetBeans ###
23+
/nbproject/private/
24+
/nbbuild/
25+
/dist/
26+
/nbdist/
27+
/.nb-gradle/
28+
build/
29+
!**/src/main/**/build/
30+
!**/src/test/**/build/
31+
32+
### VS Code ###
33+
.vscode/

SpringBoot/ThreadPool/README.md

Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# 线程池参数优化设置
2+
3+
> 线程池使用面临的核心的问题在于:线程池的参数并不好配置
4+
5+
**代码地址**
6+
7+
* Github: [https://github.com/dolyw/ProjectStudy/tree/master/SpringBoot/ThreadPool](https://github.com/dolyw/ProjectStudy/tree/master/SpringBoot/ThreadPool)
8+
* Gitee(码云): [https://gitee.com/dolyw/ProjectStudy/tree/master/SpringBoot/ThreadPool](https://gitee.com/dolyw/ProjectStudy/tree/master/SpringBoot/ThreadPool)
9+
10+
## 1. 业务场景
11+
12+
* 快速响应用户请求
13+
* 快速处理批量任务
14+
15+
### 1.1. 快速响应用户请求
16+
17+
从用户体验角度看,这个结果响应的越快越好,如果一个页面半天都刷不出,用户可能就放弃查看这个页面了。而面向用户的功能聚合通常非常复杂,伴随着调用与调用之间的级联、多级级联等情况,业务开发同学往往会选择使用线程池这种简单的方式,将调用封装成任务并行的执行,缩短总体响应时间
18+
19+
```java
20+
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
21+
corePoolSize,
22+
maxPoolSize,
23+
keepAliveSeconds, TimeUnit.SECONDS,
24+
new SynchronousQueue<>(false),
25+
new CustomizableThreadFactory(ioThreadNamePrefix),
26+
new ThreadPoolExecutor.AbortPolicy());
27+
```
28+
29+
这种场景最重要的就是获取最大的响应速度去满足用户,所以应该不设置队列去缓冲并发任务,调高 corePoolSize 和 maxPoolSize 去尽可能创造多的线程快速执行任务,使用 SynchronousQueue 任务队列,支持公平锁和非公平锁,配合拒绝策略 AbortPolicy,到达最大线程直接抛出异常,达到最快响应
30+
31+
### 2.2. 快速处理批量任务
32+
33+
离线的大量计算任务,需要快速执行,与响应速度优先的场景区别在于,这类场景任务量巨大,并不需要瞬时的完成,而是关注如何使用有限的资源,尽可能在单位时间内处理更多的任务,也就是吞吐量优先的问题。所以应该设置队列去缓冲并发任务,调整合适的 corePoolSize 去设置处理任务的线程数。在这里,设置的线程数过多可能还会引发线程上下文切换频繁的问题,也会降低处理任务的速度,降低吞吐量
34+
35+
```java
36+
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
37+
corePoolSize,
38+
maxPoolSize,
39+
keepAliveSeconds, TimeUnit.SECONDS,
40+
new ResizableCapacityLinkedBlockingQueue<>(queueCapacity),
41+
new CustomizableThreadFactory(cpuThreadNamePrefix),
42+
new ThreadPoolExecutor.CallerRunsPolicy());
43+
```
44+
45+
这个只能在测试中根据服务器资源平衡找到最优的配置,使用修改的 ResizableCapacityLinkedBlockingQueue,可以设定 capacity 参数,动态调整队列长度,在这里就不能使用拒绝策略的抛异常及丢弃任务等等,应该使用 CallerRunsPolicy,等待执行,当然也可以自定义拒绝策略,在策略执行失败日志记录,任务重新入库,重试执行几次等等
46+
47+
```java
48+
/**
49+
* 自定义拒绝策略
50+
*
51+
* @author wliduo[i@dolyw.com]
52+
* @date 2020/4/27 18:18
53+
*/
54+
public static class CustomRejectedExecutionHandler implements RejectedExecutionHandler {
55+
56+
@Override
57+
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
58+
// log("r rejected")
59+
// save r kafka mysql redis
60+
// try 3 times
61+
if(executor.getQueue().size() < 10000) {
62+
// try put again();
63+
}
64+
}
65+
}
66+
```
67+
68+
## 2. 动态化线程池
69+
70+
* 监控
71+
* 调整
72+
73+
### 2.1. 监控
74+
75+
查看当前线程池状态
76+
77+
```java
78+
/**
79+
* 线程池状态
80+
*
81+
* @param name
82+
* @return java.lang.String
83+
* @throws
84+
* @author wliduo[i@dolyw.com]
85+
* @date 2021/10/27 17:29
86+
*/
87+
@GetMapping("/state")
88+
public String state(@RequestParam("name") String name) throws Exception {
89+
ThreadPoolExecutor threadPoolExecutor = null;
90+
if (SpringUtil.getBean(name) instanceof ThreadPoolTaskExecutor) {
91+
ThreadPoolTaskExecutor threadPoolTaskExecutor = SpringUtil.getBean(name);
92+
threadPoolExecutor = threadPoolTaskExecutor.getThreadPoolExecutor();
93+
} else {
94+
threadPoolExecutor = SpringUtil.getBean(name);
95+
}
96+
CustomizableThreadFactory customizableThreadFactory = (CustomizableThreadFactory) threadPoolExecutor.getThreadFactory();
97+
StringBuffer stringBuffer = new StringBuffer("");
98+
stringBuffer.append("线程池名称: " + customizableThreadFactory.getThreadNamePrefix());
99+
stringBuffer.append("<br/>");
100+
stringBuffer.append("核心线程数: " + threadPoolExecutor.getCorePoolSize());
101+
stringBuffer.append("<br/>");
102+
stringBuffer.append("活动线程数:" + threadPoolExecutor.getActiveCount());
103+
stringBuffer.append("<br/>");
104+
stringBuffer.append("最大线程数:" + threadPoolExecutor.getMaximumPoolSize());
105+
stringBuffer.append("<br/>");
106+
stringBuffer.append("线程池活跃度(%):" + NumberUtil.div(threadPoolExecutor.getActiveCount(), threadPoolExecutor.getMaximumPoolSize()) * 100);
107+
stringBuffer.append("<br/>");
108+
stringBuffer.append("任务总数:" + threadPoolExecutor.getTaskCount());
109+
stringBuffer.append("<br/>");
110+
stringBuffer.append("任务完成数:" + threadPoolExecutor.getCompletedTaskCount());
111+
stringBuffer.append("<br/>");
112+
stringBuffer.append("队列类型:" + threadPoolExecutor.getQueue().getClass().getName());
113+
stringBuffer.append("<br/>");
114+
if (threadPoolExecutor.getQueue().size() + threadPoolExecutor.getQueue().remainingCapacity() > 0) {
115+
stringBuffer.append("队列大小:" + (threadPoolExecutor.getQueue().size() + threadPoolExecutor.getQueue().remainingCapacity()));
116+
stringBuffer.append("<br/>");
117+
stringBuffer.append("当前排队线程数:" + threadPoolExecutor.getQueue().size());
118+
stringBuffer.append("<br/>");
119+
stringBuffer.append("队列剩余大小:" + threadPoolExecutor.getQueue().remainingCapacity());
120+
stringBuffer.append("<br/>");
121+
stringBuffer.append("队列使用度(%):" + NumberUtil.div(threadPoolExecutor.getQueue().size(), threadPoolExecutor.getQueue().size() + threadPoolExecutor.getQueue().remainingCapacity()) * 100);
122+
stringBuffer.append("<br/>");
123+
}
124+
stringBuffer.append("线程活跃时间(秒):" + threadPoolExecutor.getKeepAliveTime(TimeUnit.SECONDS));
125+
stringBuffer.append("<br/>");
126+
stringBuffer.append("拒绝策略:" + threadPoolExecutor.getRejectedExecutionHandler().getClass().getName());
127+
return stringBuffer.toString();
128+
}
129+
```
130+
```bash
131+
线程池名称: jdk-thread-pool-
132+
核心线程数: 15
133+
活动线程数:10
134+
最大线程数:30
135+
线程池活跃度(%):33.333333329999995
136+
任务总数:17
137+
任务完成数:7
138+
队列类型:com.example.config.ResizableCapacityLinkedBlockingQueue
139+
队列大小:15
140+
当前排队线程数:0
141+
队列剩余大小:15
142+
队列使用度(%):0.0
143+
线程活跃时间(秒):60
144+
拒绝策略:java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy
145+
```
146+
147+
### 2.2. 调整
148+
149+
动态更新线程池参数
150+
151+
```java
152+
@Autowired
153+
private ThreadPoolExecutor jdkThreadPoolExecutor;
154+
155+
/**
156+
* 调整线程池
157+
*
158+
* @param
159+
* @return java.lang.String
160+
* @throws
161+
* @author wliduo[i@dolyw.com]
162+
* @date 2021/10/27 17:32
163+
*/
164+
@GetMapping("/set")
165+
public String set() throws Exception {
166+
jdkThreadPoolExecutor.setCorePoolSize(1);
167+
jdkThreadPoolExecutor.setMaximumPoolSize(2);
168+
jdkThreadPoolExecutor.setKeepAliveTime(120, TimeUnit.SECONDS);
169+
jdkThreadPoolExecutor.setThreadFactory(new CustomizableThreadFactory("jdk-thread-pool-new-"));
170+
jdkThreadPoolExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.AbortPolicy());
171+
ResizableCapacityLinkedBlockingQueue resizableCapacityLinkedBlockingQueue = (ResizableCapacityLinkedBlockingQueue) jdkThreadPoolExecutor.getQueue();
172+
// 设置队列长度
173+
resizableCapacityLinkedBlockingQueue.setCapacity(1);
174+
return "OK";
175+
}
176+
```
177+
178+
**参考**
179+
180+
* [Java线程池实现原理及其在美团业务中的实践](https://tech.meituan.com/2020/04/02/java-pooling-pratice-in-meituan.html)
181+
* [线程池的参数动态调整](https://www.cnblogs.com/root429/p/12799234.html)
182+
* [并发和并行的区别](https://www.jianshu.com/p/cbf9588b2afb)
183+
* [多核CPU 和多个 CPU 区别 并行和并发](https://blog.csdn.net/qq_38998213/article/details/87688929)
184+
* [Java线程池工作原理](https://blog.csdn.net/summer_fish/article/details/109952529)
185+

SpringBoot/ThreadPool/pom.xml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
4+
<modelVersion>4.0.0</modelVersion>
5+
<parent>
6+
<groupId>org.springframework.boot</groupId>
7+
<artifactId>spring-boot-starter-parent</artifactId>
8+
<version>2.5.6</version>
9+
<relativePath/> <!-- lookup parent from repository -->
10+
</parent>
11+
<groupId>com.example</groupId>
12+
<artifactId>ThreadPool</artifactId>
13+
<version>0.0.1-SNAPSHOT</version>
14+
<name>ThreadPool</name>
15+
<description>Demo project for Spring Boot</description>
16+
<properties>
17+
<java.version>1.8</java.version>
18+
</properties>
19+
<dependencies>
20+
<dependency>
21+
<groupId>org.springframework.boot</groupId>
22+
<artifactId>spring-boot-starter-web</artifactId>
23+
</dependency>
24+
25+
<dependency>
26+
<groupId>org.springframework.boot</groupId>
27+
<artifactId>spring-boot-devtools</artifactId>
28+
<scope>runtime</scope>
29+
<optional>true</optional>
30+
</dependency>
31+
<dependency>
32+
<groupId>org.springframework.boot</groupId>
33+
<artifactId>spring-boot-starter-test</artifactId>
34+
<scope>test</scope>
35+
</dependency>
36+
37+
<dependency>
38+
<groupId>cn.hutool</groupId>
39+
<artifactId>hutool-all</artifactId>
40+
<version>5.7.15</version>
41+
</dependency>
42+
</dependencies>
43+
44+
<build>
45+
<plugins>
46+
<plugin>
47+
<groupId>org.springframework.boot</groupId>
48+
<artifactId>spring-boot-maven-plugin</artifactId>
49+
</plugin>
50+
</plugins>
51+
</build>
52+
53+
</project>
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package com.example;
2+
3+
import org.springframework.boot.SpringApplication;
4+
import org.springframework.boot.autoconfigure.SpringBootApplication;
5+
6+
/**
7+
* 线程池配置
8+
*
9+
* @author wliduo[i@dolyw.com]
10+
* @date 2021/10/27 11:32
11+
*/
12+
@SpringBootApplication
13+
public class Application {
14+
public static void main(String[] args) {
15+
SpringApplication.run(Application.class, args);
16+
}
17+
18+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package com.example.config;
2+
3+
import org.slf4j.Logger;
4+
import org.slf4j.LoggerFactory;
5+
import org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler;
6+
import org.springframework.context.annotation.Configuration;
7+
import org.springframework.scheduling.annotation.AsyncConfigurer;
8+
import org.springframework.scheduling.annotation.EnableAsync;
9+
10+
import java.lang.reflect.Method;
11+
import java.util.Arrays;
12+
13+
/**
14+
* AsyncConfig
15+
*
16+
* @author wliduo[i@dolyw.com]
17+
* @date 2020/5/19 17:58
18+
*/
19+
@Configuration
20+
@EnableAsync
21+
public class AsyncConfig implements AsyncConfigurer {
22+
23+
/**
24+
* logger
25+
*/
26+
private static final Logger logger = LoggerFactory.getLogger(AsyncConfig.class);
27+
28+
/**
29+
* 这里不实现了,使用 ThreadPoolConfig 里的线程池即可
30+
*
31+
* @param
32+
* @return java.util.concurrent.Executor
33+
* @throws
34+
* @author wliduo[i@dolyw.com]
35+
* @date 2020/5/19 18:00
36+
*/
37+
/*@Override
38+
public Executor getAsyncExecutor() {
39+
return null;
40+
}*/
41+
42+
/**
43+
* 只能捕获无返回值的异步方法,有返回值的被主线程处理
44+
*
45+
* @param
46+
* @return org.springframework.aop.interceptor.AsyncUncaughtExceptionHandler
47+
* @throws
48+
* @author wliduo[i@dolyw.com]
49+
* @date 2020/5/20 10:16
50+
*/
51+
@Override
52+
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
53+
return new CustomAsyncExceptionHandler();
54+
}
55+
56+
/***
57+
* 处理异步方法中未捕获的异常
58+
*
59+
* @author wliduo[i@dolyw.com]
60+
* @date 2020/5/19 19:16
61+
*/
62+
class CustomAsyncExceptionHandler implements AsyncUncaughtExceptionHandler {
63+
64+
@Override
65+
public void handleUncaughtException(Throwable throwable, Method method, Object... obj) {
66+
logger.info("Exception message - {}", throwable.getMessage());
67+
logger.info("Method name - {}", method.getName());
68+
logger.info("Parameter values - {}", Arrays.toString(obj));
69+
if (throwable instanceof Exception) {
70+
Exception exception = (Exception) throwable;
71+
logger.info("exception:{}", exception.getMessage());
72+
}
73+
throwable.printStackTrace();
74+
}
75+
76+
}
77+
}

0 commit comments

Comments
 (0)