java线程池学习(六) —— 线程池的合理配置

时间:2021-09-20 18:02:30

一、确定线程数

在工作中,为了加快程序的处理速度,我们需要将问题分解成若干个并发执行的任务。接着我们将这些任务委派给线程,以便使它们可以并发的执行。但是需要注意的是,由于资源所限,我们不能创建过多的线程。

这就涉及到一个 确定创建多少线程数才是合理 的问题。 

《java虚拟机并发编程》一书中,对这个问题有详尽的解答,本人在此摘取归纳如下:

1.我们可以先获取到系统可用的处理器核心数:

Runtime.getRuntime().availableProcessors()


2.确定任务的类型:

如果所有任务都是计算密集型的,则创建处理器可用核心数那么多的线程数就可以了。

在这种情况下,创建更多的线程对程序的性能而言反而是不利的。因为当有多个任务处于就绪状态时,处理器核心需要在线程间频繁进行上下文切换,而这种切换对程序性能损耗较大。

如果任务都是IO密集型的,那么我们需要开更多的线程来提高性能。

当一个任务执行IO操作时,其线程被阻塞,于是处理器可以立即进行上下文切换以便处理其他就绪线程。如果我们只有处理器可用核心数那么多线程的话,则即使有待执行的任务也无法处理,因为我们已经拿不出更多的线程供处理器调度了。


3.计算出程序所需的线程数:

首先我们要明白一个概念叫 阻塞系数

如果任务有50%的时间处于阻塞状态,则阻塞系数为0.5。则程序所需的线程数为处理器可用核心数的两倍。如果任务被阻塞的时间少于50%,即这些任务是计算密集型的,则程序所需线程数将随之减少,但最少也不应该低于处理器的核心数。如果任务被阻塞的时间大于执行时间,即该任务是IO密集型的,我们就需要创建比处理器核心数大几倍数量的线程。

我们可以计算出程序所需线程的总数,总结如下:

线程数 = CPU可用核心数/(1 - 阻塞系数),其中阻塞系数的取值在0和1之间。

计算密集型人物的阻塞系数为0,而IO密集型任务的阻塞系数则接近1。


二、线程池的监控:

我们可以通过线程池提供的参数进行监控。线程池里有一些属性在监控线程池的时候可以使用

  • taskCount:线程池需要执行的任务数量。
  • completedTaskCount:线程池在运行过程中已完成的任务数量。小于或等于taskCount。
  • largestPoolSize:线程池曾经创建过的最大线程数量。通过这个数据可以知道线程池是否满过。如等于线程池的最大大小,则表示线程池曾经满了。
  • getPoolSize:线程池的线程数量。如果线程池不销毁的话,池里的线程不会自动销毁,所以这个大小只增不减。
  • getActiveCount:获取活动的线程数。

通过扩展线程池进行监控。通过继承线程池并重写线程池的beforeExecute,afterExecute和terminated方法,我们可以在任务执行前,执行后和线程池关闭前干一些事情。如监控任务的平均执行时间,最大执行时间和最小执行时间等。这几个方法在线程池里是空方法。如:

protected void beforeExecute(Thread t, Runnable r) { }

参考:《java虚拟机并发编程》一书,《 聊聊并发(三)——JAVA线程池的分析和使用