本文共 1998 字,大约阅读时间需要 6 分钟。
其实在单个处理器的时期,操作系统就能处理多线程并发任务。处理器给每个线程分配 CPU 时间片(Time Slice),线程在分配获得的时间片内执行任务。
CPU 时间片是 CPU 分配给每个线程执行的时间段,一般为几十毫秒。在这么短的时间内线程互相切换,我们根本感觉不到,所以看上去就好像是同时进行的一样。
时间片决定了一个线程可以连续占用处理器运行的时长。当一个线程的时间片用完了,或者因自身原因被迫暂停运行了,这个时候,另外一个线程(可以是同一个线程或者其它进程的线程)就会被操作系统选中,来占用处理器。这种一个线程被暂停剥夺使用权,另外一个线程被选中开始或者继续运行的过程就叫做上下文切换(Context Switch)。
具体来说,一个线程被剥夺处理器的使用权而被暂停运行,就是“切出”;一个线程被选中占用处理器开始或者继续运行,就是“切入”。在这种切出切入的过程中,操作系统需要保存和恢复相应的进度信息,这个进度信息就是“上下文”了。
那上下文都包括哪些内容呢?具体来说,它包括了寄存器的存储内容以及程序计数器存储的指令内容。CPU 寄存器负责存储已经、正在和将要执行的任务,程序计数器负责存储 CPU 正在执行的指令位置以及即将执行的下一条指令的位置。
在当前 CPU 数量远远不止一个的情况下,操作系统将 CPU 轮流分配给线程任务,此时的上下文切换就变得更加频繁了,并且存在跨 CPU 上下文切换,比起单核上下文切换,跨核切换更加昂贵。
在操作系统中,上下文切换的类型还可以分为进程间的上下文切换和线程间的上下文切换。而在多线程编程中,我们主要面对的就是线程间的上下文切换导致的性能问题,下面我们就重点看看究竟是什么原因导致了多线程的上下文切换。开始之前,先看下系统线程的生命周期状态。
结合图示可知,线程主要有 NEW、RUNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINADTED 等 6 种状态。
在这个运行过程中,线程由 RUNNABLE 转为非 RUNNABLE 的过程就是线程上下文切换。抛开新建和死亡状态,从上面的图中可以看出:上下文切换主要发生在RUNABLE状态和BLOCKED、WAITING、TIMED_WAITING这些状态的互相转换之间。
举个列子,一个线程的状态由 RUNNING 转为 BLOCKED ,再由 BLOCKED 转为 RUNNABLE ,然后再被调度器选中执行,这就是一个上下文切换的过程。
通过线程的运行状态以及状态间的相互切换,我们可以了解到,多线程的上下文切换实际上就是由多线程两个运行状态的互相切换导致的。
根据上面的分析,我们可以总结出导致上下文切换的几个原因:
第一步:用jstack命令dump线程信息,看看pid为3117的进程里的线程都在做什么。sudo -u admin /opt/ifeve/java/bin/jstack 31177 >/home/tengfei.fangtf/dump17
第二步:统计所有线程分别处于什么状态,发现300多个线程处于WAITING(onobject-monitor)状态。
[tengfei.fangtf@ifeve ~]$ grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort | uniq -c
39 RUNNABLE21 TIMED_WAITING(onobjectmonitor)6 TIMED_WAITING(parking)51 TIMED_WAITING(sleeping)305WAITING(onobjectmonitor)3 WAITING(parking)分析上面的线程的每个状态是因为什么原因造成的。
第三步:打开dump文件查看处于WAITING(onobjectmonitor)的线程在做什么。
第四步:减少JBOSS的工作线程数,找到JBOSS的线程池配置信息,将maxThreads降到100。
转载地址:http://nsazz.baihongyu.com/