线程池优化多线程

一、spring中已经实现的线程池:

1. SimpleAsyncTaskExecutor:不是真的线程池,这个类不重用线程,默认每次调用都会创建一个新的线程。

2. SyncTaskExecutor:这个类没有实现异步调用,只是一个同步操作。只适用于不需要多线程的地方。

3. ConcurrentTaskExecutor:Executor的适配类,不推荐使用。如果ThreadPoolTaskExecutor不满足要求时,才用考虑使用这个类。

4. SimpleThreadPoolTaskExecutor:是Quartz的SimpleThreadPool的类。线程池同时被quartz和非quartz使用,才需要使用此类。

5. ThreadPoolTaskExecutor :最常使用,推荐。 其实质是对java.util.concurrent.ThreadPoolExecutor的包装。

二、实际项目中使用线程池及碰到的一些问题

由于项目是微服务项目,之前的同事已经使用了spring boot封装的线程池ThreadPoolTaskExecutor ,在项目中配合@Async注解使用比较方便。这里不对线程池原理进行深入研究,只是简单介绍一下需要使用到的配置;

import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.scheduling.annotation.EnableAsync; import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; import java.util.concurrent.Executor; import java.util.concurrent.ThreadPoolExecutor; @Configuration @EnableAsync public class ExecutorConfig { // 线程池的基本大小 @Value("${threadPool.messageExcutor.corePoolSize}") private int corePoolSize; //线程池中允许的最大线程数 @Value("${threadPool.messageExcutor.maxPoolSize}") private int maxPoolSize; @Bean("messageExcutor") public Executor messageExcutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); // 线程池的基本大小 executor.setCorePoolSize(corePoolSize); //线程池中允许的最大线程数 executor.setMaxPoolSize(maxPoolSize); executor.setThreadNamePrefix("reportPush-Executor-"); //设置线程池关闭的时候等待所有任务都完成再关闭 executor.setWaitForTasksToCompleteOnShutdown(true); executor.setAwaitTerminationSeconds(60 * 15); /* 拒绝策略: 如果(总任务数 - 核心线程数 - 任务队列数)-(最大线程数 - 核心线程数)> 0 的话,则会出现线程拒绝。 举例:( 12 - 5 - 2 ) - ( 8 - 5 ) > 0,会出现线程拒绝。 线程拒绝又分为 4 种策略 CallerRunsPolicy():交由调用方线程运行,比如 main 线程。 AbortPolicy():直接抛出异常。 DiscardPolicy():直接丢弃。 DiscardOldestPolicy():丢弃队列中最老的任务。*/ executor.setRejectedExecutionHandler(new ThreadPoolExecutor. DiscardPolicy()); executor.initialize(); return executor; } }

1、线程池使用背景:本项目是原料质检系统,用户通过本系统记录质检结果,最后生成质检报告推送到erp,由于推送质检报告的时间过长,用户不想等待,所以就使用异步方式进行推送,如果推送失败后,系统会执行定时任务重新推送。

2、碰到实际问题,之前异步推送没有使用线程池统一管理线程,用户每次判定都会new一个新线程,最近一段时间系统运行突然很慢,挤压了很多线程没有执行,我整理数据后发现用户在短时间内会频繁进行推送,最多的时候一个小时进行了100多次推送操作,相当于new100多个线程,频繁的创建销毁线程导致系统资源消耗比较大,为了缓解服务器压力,决定使用线程池统一管理;

3、实际线程治理:线程池配置是直接拿的其他服务的配置,主要是核心线程数和最大线程数,线程拒绝策略的配置,由于在推送的时候,已经处理了逻辑,推送失败的质检报告会执行定时任务重新推送,不会丢失数据,加上对方法执行时间和用户操作次数的统计,核心线程设置为3,最大线程数为10,拒绝策略为直接丢弃,即减轻了服务器线程压力,数据又不会丢失,定时任务重新推送质检报告都是批量推送的,效率比多个线程单独推送更高。

4、意料之外的情况,随着公司各个功能模块的陆续上线,很多数据都是共用的,仓储系统需要用到质检系统的部分质检报告,我这边要在推送质检报告的时候,单独抽取一份数据给到仓储系统。本来是想把推送仓储系统的方法加到异步方法中的,但是忽略了推送到仓储系统的数据没做重推处理,按照线程池当前的策略,如果线程过多的话,就会直接丢弃,此时推送到erp的质检报告是没问题,有定时任务可以进行重新推送,但是推送到仓储的质检报告就会丢失。这里再说一下为什么不把推送仓储系统的质检报告方法也加到定时任务中,因为erp系统那边需要匹配其他的一些数据,匹配成功后生成质检报告单,把结果返回给质检系统,质检系统推送成功失败是根据返回结果来判定的,一个质检报告有可能重推10多次还是失败,还要进行重推,仓储系统不需要重推,所以不能加入到定时任务中。

5、解决思路:为了防止数据不丢失,最先想到的办法是调整线程池拒绝策略,使用CallerRunsPolicy()(交由调用方线程运行)策略,这样数据就不会丢失了。但是!!!这样一来,主线程很有可能会发生线程阻塞,用户推送操作都是短时间频繁操作的,如果因为网络获取查询原因,主线程执行推送方法时间过长,加上用户的频繁操作,任务会交个上层线程(主线程)执行,导致主线程既要处理其他任务,又要忙碌处理线程池的源源不断的大量任务,导致hang住。进而导致线上故障。解决一个bug,又出现另外一个bug,这样肯定是不行的。因为到仓储系统的质检报告是推送到RabbitMq中的,不需要关注对方是否成功接收,只要往Mq中成功推送数据就行。现在有两种方法,一种是直接跟随主线程推送消息,mq消息不进行异步处理,这个方法消耗时间也不多, 也不会影响用户体验;第二种方法是再创建一个线程池,拒绝策略抛出异常,我们手动把异常收集到redis中,然后定时任务定时刷新获取redis中的数据进行重发消息;由于时间原因,不能改动太多,就先使用第一种方法处理,mq消息不进行异步处理。

6、总结:个人感觉线程池看似是高大上的技术,其实使用起来并不复杂,就是需要使用者去熟悉线程池的一些配置,先知道如何使用,再在实践中不断去了解线程池技术的底层实现原理,刚开始接触的同学不需要完全把线程池技术全部弄懂,这样反而会使你对线程池有种畏惧。另外线程池技术也不要随便使用,一定要根据实际情况 去选择使用。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值