侧边栏壁纸
博主头像
孔子说JAVA博主等级

成功只是一只沦落在鸡窝里的鹰,成功永远属于自信且有毅力的人!

  • 累计撰写 377 篇文章
  • 累计创建 136 个标签
  • 累计收到 12 条评论

目 录CONTENT

文章目录

如何合理设置线程池中的线程数量

孔子说JAVA
2022-12-20 / 0 评论 / 0 点赞 / 75 阅读 / 3,002 字 / 正在检测是否收录...

线程池确实可以减少线程创建和销毁的开销,提高效率,可以复用线程。而线程池中合适的线程数量可以充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能。但是一个线程池中应该指定多少线程合适呢?指定多少核心线程,最大线程数设置为多少合适呢?其实具体的线程数量需要根据不同的服务器和不同的使用场景确定的,脱离了场景去说线程池指定多少线程合适是没有意义的。

1、为什么使用线程池

线程池其实是一种池化的技术的实现,池化技术的核心思想其实就是实现资源的一个复用,避免资源的重复创建和销毁带来的性能开销。在线程池中,线程池可以管理一堆线程,让线程执行完任务之后不会进行销毁,而是继续去处理其它线程已经提交的任务。

使用线程池的好处降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

我们都知道,使用多线程的主要目的就是提高程序的性能,具体说就是指降低延迟(发送请求到接收到数据的时间)和提高吞吐量(单位时间能可以处理更多的请求)。对应的方法有两种: 优化算法将机器的硬件性能发挥到极致

  • 优化算法:降低时间和空间复杂度,使得程序执行时间更短。
  • 将硬件的性能发挥到极致,具体指提高I/O 和cpu的利用率。

如何提高I/O 和cpu的利用率

假如在单核系统中,有一个程序执行的代码中,既有IO操作,又有Cpu计算,我们可以想一下,当程序执行IO操作的时候,Cpu其实是空闲的,反之IO是空闲的,如果这个时候用两个线程去跑这段代码,一个线程执行IO操作,一个线程执行Cpu计算,这时IO和Cpu的利用率是不是发挥到了极致呢?

image-1670810579790

上图为线程池中线程的执行流程。

thread

核心线程和非核心线程的演示效果图。

2、创建多少线程合适

调整线程池中的线程数量主要是为了充分并合理地使用 CPU 和内存等资源,从而最大限度地提高程序的性能。在设置线程池线程个数的时候,经常会想到这个问题,是不是设置的线程数越多越好?指定多少核心线程,最大线程数设置为多少合适呢?

  • 其实具体的线程数量需要根据不同的服务器和不同的使用场景确定的(即服务器的配置、服务器资源的预算和任务自身的特性),具体来说就是服务器有多少个CPU,多少内存,IO支持的最大QPS是多少,任务主要执行的是计算、IO还是一些混合操作,任务中是否包含数据库连接等的稀缺资源。脱离了场景去说线程池指定多少线程合适是没有意义的。一般情况下,我们会遇到如下三个场景:

CPU核心数或CPU并行处理的线程数(注意:这里说的是并行)在Java中可以使用 Runtime.getRuntime().availableProcessors() 命令获取到。

int  n = Runtime.getRuntime().availableProcessors();

2.1 IO密集型

线程池中的大多数任务都是执行IO操作的。IO操作时需要比较长时间的等待的,对于CPU的利用率并不高。比如数据库、文件的读写,网络通信等任务,这种任务的特点是并不会特别消耗 CPU 资源,但是 IO 操作很耗时,总体会占用比较多的时间。对于这种任务最大线程数一般会大于 CPU 核心数很多倍。

  • 因为 IO 读写速度相比于 CPU 的速度而言是比较慢的,如果设置过少的线程数,就可能导致 CPU 资源的浪费。而如果我们设置更多的线程数,那么当一部分线程正在等待 IO 的时候,它们此时并不需要 CPU 来计算,那么另外的线程便可以利用 CPU 去执行其他的任务,互不影响,这样的话在任务队列中等待的任务就会减少,可以更好地利用资源。

所以这时候可以适当的多指定一些线程,当某个线程IO阻塞的时候,让其他线程执行运算操作。线程数一般为CPU核心数的很多倍,线程数推荐计算公式如下:

IO密集型任务线程数 = CPU 核心数 *(1+平均等待时间/平均工作时间)

通过这个公式,可以计算出一个合理的线程数量,如果任务的平均等待时间长,线程数就随之增加,而如果平均工作时间长,也就是针对 CPU 密集型任务,线程数就随之减少。

2.2 CPU密集型

这类任务主要是需要执行计算操作的,响应速度会很快,这种类型的任务CPU的利用率比较高。比如加密、解密、压缩、计算等一系列需要大量耗费 CPU 资源的任务。

如果是CPU密集型的操作的话,保证每个线程的正常运作应该是效率最高的。因为如果线程数很多的话, CPU可能需要频繁的切换线程的上下文,在切换的时候,对于CPU的运算速度也是有损耗的。

  • 假设设置的线程数量是 CPU 核心数的 2 倍以上,因为计算任务非常重,会占用大量的 CPU 资源,所以这时 CPU 的每个核心工作基本都是满负荷的,而我们又设置了过多的线程,每个线程都想去利用 CPU 资源来执行自己的任务,这就会造成不必要的上下文切换,此时线程数的增多并没有让性能提升,反而由于线程数量过多会导致性能下降。

所以如果是CPU密集型的话,推荐线程个数为CPU核心数或CPU能并行处理线程数的1-2倍(注意:这里说的是并行),一般情况下可以使用如下公式:

CPU密集型任务线程数 = CPU 核心数 + 1

最好还要同时考虑在同一台机器上还有哪些其他会占用过多 CPU 资源的程序在运行,然后对资源使用做整体的平衡。

2.3 混合型任务

此类任务既需要使用CPU进行运算,有需要执行IO操作,比较常见就是Web服务器的HTTP请求处理操作。这类任务中,在任务中的CPU的运行时间相对来说占用的时间是比较少的,而IO操作的时间占用的时间是相对较长的。

针对这种任务,业界有一个相对成熟的公式:

最佳线程数目 = ((线程等待时间+线程CPU时间)/线程CPU时间 ) CPU核心数目*

如果一个请求中,计算机CPU操作使用时间占用 100ms,等待时间(如DB等IO操作)占用500ms,对于一个4核的cpu来说需要设置多少个线程合适呢?

这里我们可以有2种解决方案:

  • 方案一:拆分为两个线程池,cpu密集型的是n + 1个线程,IO密集型的就是n * 2。
  • 方案二:只有一个线程池,使用上述公式计算,(500+100)/100 ∗ 4=24,可以计算出大概使用24个线程比较合适。

当然这个只是一个估算值,还要参考具体的使用环境,硬件性能,硬盘读写速度等因素,具体使用的时候,还是需要具体的进行调试,调试到一个比较合适的值。

3、总结

太少的线程数会使得程序整体性能降低,而过多的线程会消耗内存等其他资源,所以如果想要得到更准确的线程数,可以进行压测,监控 JVM 的线程情况以及 CPU 的负载情况,根据实际情况衡量应该创建的线程数,合理并充分利用资源。

  • 1)线程的平均工作时间所占比例越高,就需要越少的线程;
  • 2)线程的平均等待时间所占比例越高,就需要越多的线程;
  • 3)针对不同的程序,进行对应的实际测试就可以得到最合适的选择。

因特网上,经常用 每秒查询率 来衡量域名系统服务器的机器的性能,即为QPS,对应fetches/sec,即每秒的响应请求数,也即是最大吞吐能力。是对一个特定的查询服务器在规定时间内所处理流量多少的衡量标准。计算关系如下:

QPS = 并发量 / 平均响应时间
并发量 = QPS * 平均响应时间
0

评论区