如果所有的任务都是计算(CPU)密集型的,则创建处理器可用核心数这么多个线程就可以了,这样已经充分利用了处理器,也就是让它以最大火力不停进行计算。创建更多的线程对于程序性能反而是不利的,因为多个线程间频繁进行上下文切换对于程序性能损耗较大。线程数=cpu核心数+1
但如果任务都是IO密集型的,那我们就需要创建比处理器核心数大几倍数量的线程。为何?当一个任务执行IO操作时,线程将被阻塞,于是处理器可以立即进行上下文切换以便处理其他就绪线程。如果我们只有处理器核心数那么多个线程的话,即使有待执行的任务也无法调度处理了。线程数=cpu核心数/(1-阻塞系数)。一般这时候的线程数是cpu核心数的好几倍。以便于线程发生I/O阻塞时cpu有可以进行线程上下文切换的线程。
如何设置线程数大小
要想合理的配置线程池,就必须首先分析任务特性,可以从以下几个角度来进行分析:
- 任务的性质:CPU密集型任务,IO密集型任务和混合型任务。
(1)CPU密集型任务配置尽可能小的线程,如配置Ncpu+1个线程的线程池。
(2)IO密集型任务则由于线程并不是一直在执行任务,则配置尽可能多的线程,如2*Ncpu。
(3)混合型的任务,如果可以拆分,则将其拆分成一个CPU密集型任务和一个IO密集型任务,只要这两个任务执行的时间相差不是太大,那么分解后执行的吞吐率要高于串行执行的吞吐率,如果这两个任务执行时间相差太大,则没必要进行分解。我们可以通过Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数。
- 任务的优先级:高,中和低。
优先级不同的任务可以使用优先级队列PriorityBlockingQueue来处理。它可以让优先级高的任务先得到执行,需要注意的是如果一直有优先级高的任务提交到队列里,那么优先级低的任务可能永远不能执行。
- 任务的执行时间:长,中和短。
执行时间不同的任务可以交给不同规模的线程池来处理,或者也可以使用优先级队列,让执行时间短的任务先执行。 -
任务的依赖性:是否依赖其他系统资源,如数据库连接。
依赖数据库连接池的任务,因为线程提交SQL后需要等待数据库返回结果,如果等待的时间越长CPU空闲时间就越长,那么线程数应该设置越大,这样才能更好的利用CPU。
补充:调节线程池大小的另一种方法如下
给定以下定义:
Ncpu:CPU的数量(Runtime.getRuntime().availableProcessors()方法获得当前设备的CPU个数)
Ucpu:target CPU utilization ,1 <= Ucpu <= 1
W/C:W为任务的等待时间,C为任务的运行时间
要使处理器达到期望的使用率,线程池的最优大小为:Ncpu * Ucpu * (1 + W/C)
IO密集型应用 - 阻塞系数大,建议多一点的线程数 - 建议为2倍CPU数,即假设等待时间和线程CPU时间一样,阻塞系数为0.5
CPU计算密集型应用 - 线程等待时间少,建议少一点的线程数 - 一般建议为CPU数,即假设线程等待时间为0,阻塞系数为0
最佳线程数 cpu数/1-阻塞系数
阻塞系数 线程等待时间/ 线程等待时间 + 线程CPU时间