|
| 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 | + |
0 commit comments