一. 前言

Java并发编程方面,如何在多线程环境中设置合理的线程数,那我们需要了解两个概念:

计算密集型
要进行大量的计算、逻辑判断等操作,消耗CPU资源,比如计算圆周率、对视频进行高清解码等等,全靠CPU的运算能力。
这种计算密集型任务虽然也可以用多任务完成,但是任务越多,花在任务切换的时间就越多,CPU执行任务的效率就越低,所以,要最高效地利用CPU,计算密集型任务同时进行的数量应当等于CPU的核心数,我们设置线程数一般是:计算机核数n+1

计算密集型任务由于主要消耗CPU资源,因此,代码运行效率至关重要。Python这样的脚本语言运行效率很低,完全不适合计算密集型任务。对于计算密集型任务,最好用C语言编写。

IO密集型
涉及到网络、磁盘IO的任务都是IO密集型任务,这类任务的特点是CPU消耗很少,任务的大部分时间都在等待IO操作完成(因为IO的速度远远低于CPU和内存的速度)。一旦发生IO,线程就会处于等待状态,当IO结束,数据准备好后,线程才会继续执行。.对于IO密集型任务,任务越多,CPU效率越高,但也有一个限度。常见的大部分任务都是IO密集型任务,比如Web应用。

IO密集型任务执行期间,99%的时间都花在IO上,花在CPU上的时间很少,因此,用运行速度极快的C语言替换用Python这样运行速度极低的脚本语言,完全无法提升运行效率。对于IO密集型任务,最合适的语言就是开发效率最高(代码量最少)的语言,脚本语言是首选,C语言最差。

二. CPU 使用率

1、CPU 使用率怎么计算

CPU% = 1 - idleTime / sysTime * 100

  • idleTime:CPU处于空闲状态的时间
  • sysTime:CPU处于用户态和内核台的时间总和

2、CPU 使用率跟啥有关系?

常听说计算密集型的程序是比较耗 CPU 使用率的。

那 JAVA 应用中哪些操作是比较耗 CPU 使用的?

列举日常程序中常见的耗CPU的操作:

  • 频繁GC,访问量高时,有可能造成频繁的GC、甚至FGC。当调用量大时,内存分配过快,就会造成GC线程不停的执行,导致CPU飙高
  • 序列化与反序列化,后文中举了一个真实的案例,程序执行xml解析的时,调用量增大的情况下,导致了CPU被打满
  • 加密、解密
  • 正则表达式校验,曾经线上发生一次血案,正则校验将CPU打满。大概原因是:Java 正则表达式使用的引擎实现是 NFA 自动机,这种引擎在进行字符匹配会发生回溯(backtracking)
  • 线程上下文切换、当启动了很多线程,而这些线程都处于不断的阻塞状态(锁等待、IO等待等)和执行状态的变化过程中。当锁竞争激烈时,很容易出现这种情况
  • 某些线程在做无阻塞的运算,简单的例子while(true)中不停的做运算,没有任何阻塞。写程序时,如果需要做很久的计算,可以适当将程序sleep下

3、CPU 与进程、线程有关系么?

现在分时操作系统是通过循轮方式分配时间片进行进程调度的,如果进程在等待或阻塞,不会造成 CPU 资源使用。线程称为轻进程,共享进程资源,关于线程的调度,CPU 对于线程也是分时调度。而在 Java 中,线程的调用由 JVM 负责,线程的调度一般有两种模式,分时调度和抢占式调度。

作为一个Java开发者,会不会有这样的疑问?

  • 一个 while 死循环,会不会引起 CPU 使用率飚升?
  • 频繁 Young GC 会不会引起 CPU 使用率飚升?
  • 线程数很高的应用,CPU 使用率一定高么?
  • CPU 使用率高的应用,线程数一定高么?
  • BLOCKED 状态的线程会不会引起 CPU 使用率飚升?
  • 分时操作系统 CPU 是耗费 us ? 还是耗费 sy ?

三. 解惑 

1、一个 while 死循环,会不会引起 CPU 使用率飚升?

会的。

先不说别的,死循环会调用 CPU 寄存器进行计数,这个操作就会占用 CPU。其次,如果线程一直处于死循环状态,CPU 调用会进行线程切换么?

死循环不会让出 CPU,除非操作系统时间片到期,但死循环会不断向系统申请时间片,直到系统没有空闲时间做别的事情。

这个问题在 stackoverflow 也有人提问:why does an infinite loop of the unintended kind increase the CPU use?

地址:https://stackoverflow.com/questions/2846165/why-does-an-infinite-loop-of-the-unintended-kind-increase-the-cpu-use

2、频繁 Young GC 会不会引起 CPU 使用率飚升?

会的。

Young GC 本身是 JVM 进行垃圾回收的操作,会计算内存和调用寄存器,频繁 Young GC 一定是会占用 CPU。

之前有个一个案例,for 循环从数据库查询数据集合,二次封装新的数据集合,这时如果量比较大时,内存没有足够的空间存储,那么 JVM 就会 GC 回收那些不再使用的数据,因此量大的时候,就会收到 CPU 使用率报警。

3、线程数很高的应用,CPU 使用率一定高么?

不会。

通过 jstack 查看系统线程状态,查看整个线程数很多,但 Runable 和 Running 状态的线程不多,这时 CPU 使用率不一定会高。

之前有过一个案例,查看系统线程数 1000+,jstack 分析 900多个线程是 BLOCKED 和 WAITING 状态的,这种线程是不会占用 CPU 的。

如果线程数很高,其实大多数原因是死锁,大量线程处于 BLOCKED 和 WAITING 状态。

4、CPU 使用率高的应用,线程数一定高么?

不会。

同上,CPU 使用率高的关键因素还是计算密集型操作,一个线程如果有大量计算,也会造成 CPU 使用率高,也是现在为什么一个大数据脚本任务,要大规模集群共同运算才能运行的原因。

5、BLOCKED 状态的线程会不会引起 CPU 使用率飚升?

不一定。

CPU使用率的飙升,更多是因为上下文的切换或者runnable状态线程过多导致。Blocked状态,未必会引起CPU上升。

6、分时操作系统 CPU us高或者sy高是什么意思?

通过top命令,可以观察到CPU的us,sy值,示例如下:

  • us 用户空间占用CPU百分比,简单来说,us高是因为程序导致的,通过分析线程堆栈,可以很容易的定位到问题线程。
  • sy 内核空间占用CPU百分比,sy高的时候,如果是程序问题导致,基本是因为线程上下文切换造成的。

四、经验 

平时怎么定位 CPU 使用率高的原因?网上有个教程和方法,下面简述一下分析过程。首先发现某台应用 CPU 使用率高,一要看先线程数、JVM、系统 load 等参数,共同作证。二要打印 jstack,通过工具分析线程情况,推荐 fastThread 这个在线的 Thread 分析工具。

以下是线上发生的真实案例,简要介绍下:

某日晚,突然收到短信报警,CPU利用率100%。立刻dump该机器jstack,通过 http://fastthread.io/ 查看日志如下:

进一步查看具体日志:

通过这段日志,已经定位到了具体CPU被打满的方法,接收MQ之后,MQ消息体为xml,反序列化的时候,造成了CPU飙高。 

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐