1.背景
如果说如何提升系统的并发能力,大家第一想到的肯定是采用多线程,而提到多线程编程,就不得不说到线程池,合理的使用线程池可以帮助我们大大提升系统的性能,但前提是“合理”两字,如果乱用/滥用线程池,带来的可能不是高性能而是灾难了。最近在一个需求当中,因为涉及到多方调用,自然而然的想到了使用线程池来提升性能,但由于线程池数量设置的不合理,导致在系统负载很低的情况下出现了线程阻塞的问题,不但没有提高接口的性能,反而让接口被线程池拖挂了。问题发生后,针对这一问题做了复盘,趁此机会也对线程池的原理和使用做了一番探究,记录在此,避免后续再犯同样的错误。
2.线程和线程池的原理
俗话说知己知彼,才能百战百胜,要想使用好线程池,必须要知道线程和线程池的原理,这部分知识已经在网上已经有大量的资料,我这边就不再赘述,这里放上2篇我认为写的比较好的关于线程和线程池的文章。
编程思想之多线程与多进程(1)——以操作系统的角度述说线程与进程
深入理解Java线程池:ThreadPoolExecutor
3.合理使用线程池
基于第二部分的知识,我们知道线程是一种稀缺的资源,频繁的创建销毁线程会大大降低系统的性能,所以才有了线程池来解决这个问题(池化的思想其实是一种通用的解决方案,用于解决一些资源创建耗费高昂,但又需要多次重复使用的问题,如数据库连接池)。线程池虽然解决了线程频繁创建销毁的问题,但他并不是解决多线程问题的银弹,必须要合理使用线程池,才能发挥线程池最大的功效,否则很可能适得其反,下面是我总结的一些关于合理使用线程池的点。
3.1设置线程池的大小
合理设置线程池大小很重要,前面提到的线上问题就是因为没有合理设置线程池大小才发生的,之所以要使用线程池就是因为线程是一种耗费较高的资源,我们既不能在系统中无限创建线程,那样会导致系统资源枯竭,也不能创建过少的线程,那样会导致无法充分利用CPU资源。
设置线程池大小的问题也属于老生长谈了,网上一搜索就会有各种各样的文章博客,这里就参考《java concurrency in practice》这本书做个总结吧。
- 合理的设置线程池大小取决于任务的类型,针对于计算密集型的任务,一般设置N+1个线程(N指CPU的核数,理想情况下设置N个线程是对CPU使用的效率最高的,但是即使是计算密集型的任务,也有可能因为一些其他原因中断导致线程切换,所以额外设置一个线程)
- 如果是IO密集型或者存在阻塞操作的任务,则需要设置大一点的线程数,有一些博客上会写到IO密集型设置2N+1的线程数,其实是不正确的,实际上针对IO密集的任务,需要看真正的线程CPU使用时间和线程等待时间的比例,书中给了一个公式:给定N=CPU的核数,U=目标CPU使用率(0<=U<=1),W/C = 线程等待时间/线程CPU使用时间。一个合理的线程池数量=N * U * ( 1 + W/C)。
我们在开发过程中遇到的最多的IO密集型任务就是远程接口调用了,一般来说,远程接口调用,W/C的比例一般在9以上,即