在看这个问题之前一定要明白的是线程(应用程序)是不能控制CPU的计算资源分配的。他只是将任务提交给操作系统,至于CPU资源如何分配是由操作系统来控制的。

对于多线程下的解决方案,并不是线程量越多就越好,线程池数量的设置要考虑到很多方面,首先要确认这个线程池所处理的任务是属于CPU密集型还是IO密集型或者属于混合型。对于CPU密集型一般线程池数量为CPU核数+1,+的这个1是充分利用CPU(如当某个线程出现空闲时或一个线程计算结束切换新任务时能够充分利用这部分资源),而对于IO密集型,一般设置为2N(CPU数)+1.但是这并不是绝对的,要考虑到IO的响应时间,如果响应时间比较长会导致CPU空闲的情况可以适当增加线程数。但即使是这种情况也并不是可以无限制的增大,因为还要考虑到IO的压力。如针对数据库,如果有过多的线程同时连接数据库使得数据库连接数过多会增大数据库的压力。这个在设置线程池大小时也要考虑进去。

对线程数量新的理解:

纯计算型

CPU密集型所谓的N+1根本没有意义。如果是纯计算型,那么N个CPU就能把CPU打满,多加了这个1反而可能会导致线程切换(应用中仅有N个线程)或者是提升了线程切换的概率(应用还有其他线程或进程)。

这个+1常见的说法就是上面我曾经的理解: 如当某个线程出现空闲时或一个线程计算结束切换新任务时能够充分利用这部分资源。   

这个说法可能来自于《Java Concurrency In Practise》8.2节对于计算密集型的任务,在拥有N个处理器的系统上,当线程池的大小为N+1时,通常能实现最优的效率。(即使当计算密集型的线程偶尔由于缺失故障或者其他原因而暂停时,这个额外的线程也能确保CPU的时钟周期不会被浪费。)

但是这个说法,并不完全对,首先,线程执行过程中因为业务异常或中断退出,那么这个线程会在final块内执行processWorkerExit方法,这个方法会重新添加一个非核心线程到池子中(这个可能导致在任务队列没满的时间,线程量就大于了核心线程,比如我这边还没添加成功时,一个新任务被提交,添加了一个核心线程,然后这边在执行异常补充线程流程)。这个过程中CPU依然被完全占用的, 并不存在所谓的替补线程能替补上来的情况。

另外一种情况是线程是被kill掉的情况,那么线程池会在任务提交时符合条件的情况下重新建立线程。 而线程kill到任务提交满足条件重新创建的线程过程确实可能造成一个U被浪费(前提是你的应用除了线程池线程,其他线程(包括任务提交线程)都处于非计算状态(比如阻塞或者IO)),否则空出来的这个U也会被其他线程池外的任务利用起来。

混合型

对于混合型,2N+1太过粗暴了,根本没考虑混合型的利用情况,而是假设CPU占据任务总时间的50%。

实际的计算公式可以用这个公式估算: 

估计线程量   = (CPU数/(任务CPU运算时间/任务总时间))*你期望的CPU利用率百分比

或者是

估计线程量   = CPU数*(任务总时间/任务CPU运算时间)*你期望的CPU利用率百分比

两个公式一样,看哪个容易理解。

但是注意,这只是估算,要想获得最佳的值,根据业务调整,或者提前进行压测获得才是正道。

为什么混合型考虑了CPU利用率百分比而密集型不考虑呢?这是因为混合型任务多于CPU,可以通过任务切换把计算压力分散一下,通过空一些时间片而控制利用率。而密集型则不行,对于密集型,即使任务数低于CPU,那么也不会产生切换,而是拿到任务的CPU一直执行,比如说4个CPU, 但是只设置了3个线程,那么就会有3个CPU一直打满,一个CPU完全空闲。而不会说一个CPU各算一段这样。