Day795.监测上下文切换异常的命令排查工具BlockingQueue -Java 性能调优实战
创始人
2024-04-05 16:27:34
0

监测上下文切换异常的命令排查工具&BlockingQueue

Hi,我是阿昌,今天学习记录的是关于监测上下文切换异常的命令排查工具&BlockingQueue的内容。


一、使用系统命令查看上下文切换

1、Linux 命令行工具之 vmstat 命令

vmstat 是一款指定 采样周期和次数的功能性监测工具,可以使用它监控进程上下文切换的情况。

在这里插入图片描述

vmstat 1 3 命令行代表每秒收集一次性能指标,总共获取 3 次。

以下为上图中各个性能指标的注释:

  • procs
    • r:等待运行的进程数
    • b:处于非中断睡眠状态的进程数
  • memory
    • swpd:虚拟内存使用情况
    • free:空闲的内存
    • buff:用来作为缓冲的内存数
    • cache:缓存大小
  • swap
    • si:从磁盘交换到内存的交换页数量
    • so:从内存交换到磁盘的交换页数量
  • io
    • bi:发送到块设备的块数
    • bo:从块设备接收到的块数
  • system
    • in:每秒中断数
    • cs:每秒上下文切换次数
  • cpu
    • us:用户 CPU 使用时间
    • sy:内核 CPU 系统使用时间
    • id:空闲时间
    • wa:等待 I/O 时间
    • st:运行虚拟机窃取的时间

2、Linux 命令行工具之 pidstat 命令

通过上述的 vmstat 命令只能观察到哪个进程的上下文切换出现了异常,那如果是要查看哪个线程的上下文出现了异常呢?

pidstat 命令就可以帮助我们监测到具体线程的上下文切换。

pidstat 是 Sysstat 中一个组件,也是一款功能强大的性能监测工具。

可以通过命令 yum install sysstat 安装该监控组件。

通过 pidstat -help 命令,可以查看到有以下几个常用参数可以监测线程的性能:

在这里插入图片描述

常用参数
-u:默认参数,显示各个进程的 cpu 使用情况;
-r:显示各个进程的内存使用情况;
-d:显示各个进程的 I/O 使用情况;
-w:显示每个进程的上下文切换情况;
-p:指定进程号;
-t:显示进程中线程的统计信息

首先,通过 pidstat -w -p pid 命令行,可以查看到进程的上下文切换:

在这里插入图片描述

  • cswch/s:每秒主动任务上下文切换数量
  • nvcswch/s:每秒被动任务上下文切换数量

之后,通过 pidstat -w -p pid -t 命令行,可以查看到具体线程的上下文切换:

在这里插入图片描述

3、JDK 工具之 jstack 命令

查看具体线程的上下文切换异常,还可以使用 jstack 命令查看线程堆栈的运行情况。

jstack 是 JDK 自带的线程堆栈分析工具,使用该命令可以查看或导出 Java 应用程序中的线程堆栈信息。

jstack 最常用的功能就是使用 jstack pid 命令查看线程堆栈信息,通常是结合 pidstat -p pid -t 一起查看具体线程的状态,也经常用来排查一些死锁的异常。

在这里插入图片描述

每个线程堆栈的信息中,都可以查看到线程 ID、线程状态(wait、sleep、running 等状态)以及是否持有锁等。

可以通过 jstack 16079 > /usr/dump 将线程堆栈信息日志 dump 下来,之后打开 dump 文件,通过查看线程的状态变化,就可以找出导致上下文切换异常的具体原因。

例如,系统出现了大量处于 BLOCKED 状态的线程,就需要立刻分析代码找出原因。


二、多线程队列

在 Java 多线程应用中,特别是在线程池中,队列的使用率非常高。

Java 提供的线程安全队列又分为了阻塞队列非阻塞队列

1、阻塞队列

阻塞队列可以很好地支持生产者和消费者模式的相互等待,当队列为空的时候,消费线程会阻塞等待队列不为空;当队列满了的时候,生产线程会阻塞直到队列不满。

在 Java 线程池中,也用到了阻塞队列。

当创建的线程数量超过核心线程数时,新建的任务将会被放到阻塞队列中。

可以根据自己的业务需求来选择使用哪一种阻塞队列,阻塞队列通常包括以下几种:

  • ArrayBlockingQueue:一个基于数组结构实现的有界阻塞队列,按 FIFO(先进先出)原则对元素进行排序,使用 ReentrantLock、Condition 来实现线程安全;
  • LinkedBlockingQueue:一个基于链表结构实现的阻塞队列,同样按 FIFO (先进先出) 原则对元素进行排序,使用 ReentrantLock、Condition 来实现线程安全,吞吐量通常要高于 ArrayBlockingQueue;
  • PriorityBlockingQueue:一个具有优先级的无限阻塞队列,基于二叉堆结构实现的无界限(最大值 Integer.MAX_VALUE - 8)阻塞队列,队列没有实现排序,但每当有数据变更时,都会将最小或最大的数据放在堆最上面的节点上,该队列也是使用了 ReentrantLock、Condition 实现的线程安全;
  • DelayQueue:一个支持延时获取元素的无界阻塞队列,基于 PriorityBlockingQueue 扩展实现,与其不同的是实现了 Delay 延时接口;、
  • SynchronousQueue:一个不存储多个元素的阻塞队列,每次进行放入数据时, 必须等待相应的消费者取走数据后,才可以再次放入数据,该队列使用了两种模式来管理元素,一种是使用先进先出的队列,一种是使用后进先出的栈,使用哪种模式可以通过构造函数来指定。

Java 线程池 Executors 还实现了以下四种类型的 ThreadPoolExecutor,分别对应以上队列,详情如下:

在这里插入图片描述

2、非阻塞队列

常用的线程安全的非阻塞队列是 ConcurrentLinkedQueue,它是一种无界线程安全队列 (FIFO),基于链表结构实现,利用 CAS 乐观锁来保证线程安全。

下面通过源码来分析下该队列的构造、入列以及出列的具体实现。

构造函数

ConcurrentLinkedQueue 由 head 、tail 节点组成,每个节点(Node)由节点元素(item)和指向下一个节点的引用 (next) 组成,节点与节点之间通过 next 关联,从而组成一张链表结构的队列。在队列初始化时, head 节点存储的元素为空,tail 节点等于 head 节点。


public ConcurrentLinkedQueue() {head = tail = new Node(null);
}private static class Node {volatile E item;volatile Node next;..
}

入列

当一个线程入列一个数据时,会将该数据封装成一个 Node 节点,并先获取到队列的队尾节点,当确定此时队尾节点的 next 值为 null 之后,再通过 CAS 将新队尾节点的 next 值设为新节点。此时 p != t,也就是设置 next 值成功,然后再通过 CAS 将队尾节点设置为当前节点即可。


public boolean offer(E e) {checkNotNull(e);//创建入队节点final Node newNode = new Node(e);//t,p为尾节点,默认相等,采用失败即重试的方式,直到入队成功         for (Node t = tail, p = t;;) {//获取队尾节点的下一个节点Node q = p.next;//如果q为null,则代表p就是队尾节点if (q == null) {//将入列节点设置为当前队尾节点的next节点if (p.casNext(null, newNode)) {//判断tail节点和p节点距离达到两个节点if (p != t) // hop two nodes at a time//如果tail不是尾节点则将入队节点设置为tail。// 如果失败了,那么说明有其他线程已经把tail移动过 casTail(t, newNode);  // Failure is OK.return true;}}// 如果p节点等于p的next节点,则说明p节点和q节点都为空,表示队列刚初始化,所以返回  else if (p == q)p = (t != (t = tail)) ? t : head;else// Check for tail updates after two hops.p = (p != t && t != (t = tail)) ? t : q;}}

出列

首先获取 head 节点,并判断 item 是否为 null,如果为空,则表示已经有一个线程刚刚进行了出列操作,然后更新 head 节点;如果不为空,则使用 CAS 操作将 head 节点设置为 null,CAS 就会成功地直接返回节点元素,否则还是更新 head 节点。

public E poll() {// 设置起始点restartFromHead:for (;;) {//p获取head节点for (Node h = head, p = h, q;;) {//获取头节点元素E item = p.item;//如果头节点元素不为null,通过cas设置p节点引用的元素为nullif (item != null && p.casItem(item, null)) {// Successful CAS is the linearization point// for item to be removed from this queue.if (p != h) // hop two nodes at a timeupdateHead(h, ((q = p.next) != null) ? q : p);return item;}//如果p节点的下一个节点为null,则说明这个队列为空,更新head结点else if ((q = p.next) == null) {updateHead(h, p);return null;}//节点出队失败,重新跳到restartFromHead来进行出队else if (p == q)continue restartFromHead;elsep = q;}}}

ConcurrentLinkedQueue 是基于 CAS 乐观锁实现的,在并发时的性能要好于其它阻塞队列,因此很适合作为高并发场景下的排队队列。


相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...