最近在工作的时候跟一个同事A合作了几个业务点,发现A用了多线程做优化,发现了几点问题,做一下记录
1. A 使用多线程时,在方法中创建了自己的线程池
2. 线程池的大小、队列设置不合理
3. 使用多线程后对业务产生的影响
下面逐一讨论下浅见:
1. 对于一些程序员来说,在业务中随处使用线程池,也就是自己创建自己的线程池,这种方式没有根本上的错误,但这造成了哪些影响呢。
首先我认为对线程池的使用应该是谨慎小心的,对于单个接口、业务来说,多线程处理可以解决接口处理大量数据、复杂业务缓慢的问题,但我们应该在应用中维护开发者自定义的、公共线程池,不应该随用随建,这样涉及到线程池的管理,在业务结束时需要关闭、销毁线程池,这给我们的系统带来的问题是:不断创建了大量的线程池,并且不断去销毁,产生了不必要的内存消耗。
2. A 同学使用了corePoolSize(10)、maximumPoolSize (20)、LinkedBlockingQuene(100)这样的参数,可以看出这是多么的随意,没有考虑到此处业务的数据量,这样的线程池能抗的住并发吗?对现有业务能产生多大的优化呢?
可以看出,当现有业务并发处理到120个时,会产生队列丢弃,而且当数据量持续增大时,系统数据会出错而线上排查困难。
应该怎么做呢,在优化时做数据量、业务量的预期,比如说单应用最大500个客户的1000条数据的处理,corePoolSize(30)、maximumPoolSize (100)这样的初始化参数,业务内部做子线程处理数据时分为10*100,那么线程池的设计应该最少支持500*10-100的任务队列。
3. A在使用多线程时,内部子线程的数量是根据list表数量来分的,造成了什么结果呢?
a. 对应用内部来说,后面的数据库查询会产生 list.size() 次查询
b. 对于下游微服务来说产生了 list.size() 次调用,
可以想象,这是灾难性的使用,用户的一次接口调用,内部竟然产生了如此大量的性能、内存消耗,很有可能将下游微服务接口拖垮,并产生大量的无用的调用日志。
应该怎么做呢?对于 list 表处理,应该对list表进行合理分页,将大数据量的一次处理改成少数据量的多次处理,这样对于应用自己的数据库查询效率提高很多,对下游服务的调用次数也大大减少,方可达到优化的效果并对系统稳健性不产生影响。
解决方案:
1. 对于这样的业务处理,并且处理完成后,要做list结果的返回,那么使用了多线程的情况下,还要做最终的汇总返回,我建议使用线程池和 CountDownLatch 处理,CountDownLatch是一个主线程等待子线程的工作模型,初始化方法 CountDownLatch countDownLatch = new CountDownLatch(threadNum), threadNum=10 即为子线程数量,子线程执行完毕后调用 countDownLatch.countDown() 方法做计数器扣减,等待方法countDownLatch.await()等待threadNum = 0 ,所有的子线程处理完毕时会继续执行,做汇总的业务处理、返回。
2. 另外并发包下还有 CyclicBarrier、Semaphore、Exchanger、Phaser等并发工具
下一篇会写一下 CyclicBarrier的使用,感谢关注,搜索公众号 gosling9527