[Java Performance] 线程及同步的性能 - 线程池/ThreadPoolExecutors/ForkJoinPool

本文详细探讨了Java中的线程池和ThreadPoolExecutors,强调了线程池大小对性能的影响。通过实例分析了最大线程数的设定,指出计算密集型任务的最佳线程数通常是CPU核心数。同时,文章讨论了最小线程数的设置,建议通常设置为最大线程数。此外,文章还介绍了线程池任务数量的考量和ThreadPoolExecutor的定制策略。最后,文章对比了ForkJoinPool的特点,包括自动并行化和工作窃取,展示了ForkJoinPool在处理大量任务和任务负载不均衡时的优势。
摘要由CSDN通过智能技术生成

线程池和ThreadPoolExecutors

虽然在程序中可以直接使用Thread类型来进行线程操作,但是更多的情况是使用线程池,尤其是在Java EE应用服务器中,一般会使用若干个线程池来处理来自客户端的请求。Java中对于线程池的支持,来自ThreadPoolExecutor。一些应用服务器也确实是使用的ThreadPoolExecutor来实现线程池。

对于线程池的性能调优,最重要的参数就是线程池的大小。

对于任何线程池而言,它们的工作方式几乎都是相同的:

  • 任务被投放到一个队列中(队列的数量不定)
  • 线程从队列中取得任务并执行
  • 线程完成任务后,继续尝试从队列中取得任务,如果队列为空,那么线程进入等待状态

线程池往往拥有最小和最大线程数:

  • 最小线程数,即当任务队列为空时,线程池中最少需要保持的线程数量,这样做是考虑到创建线程是一个相对耗费资源的操作,应当尽可能地避免,当有新任务被投入队列时,总会有线程能够立即对它进行处理。
  • 最大线程数,当需要处理的任务过多时,线程池能够拥有的最大线程数。这样是为了保证不会有过多的线程被创建出来,因为线程的运行需要依赖于CPU资源和其它各种资源,当线程过多时,反而会降低性能。

在ThreadPoolExecutor和其相关的类型中,最小线程数被称为线程池核心规模(Core Pool Size),在其它Java应用服务器的实现中,这个数量也许被称为最小线程数(MinThreads),但是它们的概念是相同的。

但是在对线程池进行规模变更(Resizing)的时候,ThreadPoolExecutor和其它线程池的实现也许存在的很大的差别。

一个最简单的情况是:当有新任务需要被执行,且当前所有的线程都被占用时,ThreadPoolExecutor和其它实现通常都会新创建一个线程来执行这个新任务(直到达到了最大线程数)。

设置最大线程数

最合适的最大线程数该怎么确定,依赖以下两个方面:

  • 任务的特征
  • 计算机的硬件情况

为了方便讨论,下面假设JVM有4个可用的CPU。那么任务也很明确,就是要最大程度地“压榨”它们的资源,千方百计的提高CPU的利用率。

那么,最大线程数最少需要被设置成4,因为有4个可用的CPU,意味着最多能够并行地执行4个任务。当然,垃圾回收(Garbage Collection)在这个过程中也会造成一些影响,但是它们往往不需要使用整个CPU。一个例外是,当使用了CMS或者G1垃圾回收算法时,需要有足够的CPU资源进行垃圾回收。

那么是否有必要将线程数量设置的更大呢?这就取决于任务的特征了。

假设当任务是计算密集型的,意味着任务不需要执行IO操作,例如读取数据库,读取文件等,因此它们不涉及到同步的问题,任务之间完全是独立的。比如使用一个批处理程序读取Mock数据源的数据,测试在不线程池拥有不同线程数量时的性能,得到下表:

线程数 执行时间(秒) 基线百分比
1 255.6 100%
2 134.8 52.7%
4 77.0 30.1%
8 81.7 31.9%
16 85.6 33.5%

从上面中得到一些结论:

  • 当线程数为4时,达到最优性能,再增加线程数量时并没有更好的性能,因为此时CPU的利用率已经达到了最高,在增加线程只会增加线程之间争夺CPU资源的行为,因此反而降低了性能。
  • 即使在CPU利用率达到最高时,基线百分比也不是理想中的25%,这是因为虽然在程序运行过程中,CPU资源并不是只被应用程序线程独享的,一些后台线程有时也会需要CPU资源,比如GC线程和系统的一些线程等。

当计算是通过Servlet触发的时候,性能数据是下面这个样子的(Load Generator会同时发送20个请求):

线程数 每秒操作数(OPS) 基线百分比
4 77.43 100%
8 75.93 98.8%
16 71.65 92.5%
32 69.34 89.5%
64 60.44 78.1%

从上表中可以得到的结论:

  • 在线程数量为4时,性能最优。因为此任务的类型是计算密集型的,只有4个CPU,因此线程数量为4时,达到最优情况。
  • 随着线程数量逐渐增加,性能下降,因为线程之间会互相争夺CPU资源,造成频繁切换线程执行上下文环境,而这些切换只会浪费CPU资源。
  • 性能下降的速度并不明显,这也是因为任务类型是计算密集型的缘故,如果性能瓶颈不是CPU提供的计算资源,而是外部的资源,如数据库,文件操作等,那么增加线程数量带来的性能下降也许会更加明显。

下面,从Client的角度考虑一下问题,并发Client的数量对于Server的响应时间会有什么影响呢?还是同样地环境,当并发Client数量逐渐增加时,响应时间会如下发生变化:

并发Client线程数 平均响应时间(秒) 基线百分比
1 0.05 100%
2 0.05 100%
4 0.05 100%
6 0.076 152%
8 0.104 208%
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值