并发编程(三):线程快照统计・grep+awk+sort+uniq 实战详解

上一篇咱们讲了 jstack dump 线程快照,其中最关键的一步就是用「grep+awk+sort+uniq -c」这串 指令,快速统计线程状态分布,避开了手动翻几百行快照的麻烦。

其实它本质就是 4 个 "小工具" 流水线干活,今天咱们就来讲一讲~


一、这串指令到底在干一件什么事?

咱们先回到上一篇的场景:导出的 dump 文件有几百行,里面混着各种线程信息,我们的核心需求是「快速统计每种线程状态有多少个」。

这串指令:

bash 复制代码
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort | uniq -c

本质就是 "筛选→提取→排序→计数" 的流水线作业 ,咱们用 "整理文件" 类比,一下子就能懂:

  1. 第一步精准过滤(grep):从一堆文件(dump17)里,只挑出 "写了线程状态" 的纸条(比如只留 "java.lang.Thread.State: WAITING" 这类行);
  2. 第二步提取关键字(awk):把挑出来的纸条,只保留 "线程状态" 这几个字(比如把 "java.lang.Thread.State: WAITING (on object monitor)" 简化成 "WAITING (onobjectmonitor)");
  3. 第三步排序(sort):把相同状态的纸条排在一起(比如所有 "WAITING" 纸条放一堆,"RUNNABLE" 纸条放一堆);
  4. 第四步去重并计数(uniq -c):数每堆纸条有多少张,在纸条前面写上数字(比如 "305 WAITING (onobjectmonitor)")。

整个过程,就像你整理办公室的文件,先挑有用的、再提炼关键信息、再分类、最后统计数量。


二、4 个指令

咱们逐个拆解这 4 个指令,只讲和线程快照统计相关的用法。

第一步:grep ------ 「文件里找东西的神器」(筛选)

grep 就像 "找东西的放大镜",核心作用是:从指定文件里,筛选出 "包含某个关键词" 的所有行,没用的行直接过滤掉。

线程快照里的核心用法
bash 复制代码
grep java.lang.Thread.State dump17
说明:
  • 关键词:java.lang.Thread.State ------ 这是 Java 线程状态的固定前缀(dump 文件里,所有线程状态行都会包含这个字符串);

  • 目标文件:dump17 ------ 我们上一篇导出的线程快照文件;

  • 执行结果:会把 dump17 里,所有包含 "线程状态" 的行都输出来,示例如下(只截取 2 行)

    复制代码
    java.lang.Thread.State: RUNNABLE
    java.lang.Thread.State: WAITING (on object monitor)
grep 高频参数

不用记太多,掌握这 4 个,能解决 90% 的筛选需求

参数 解释 线程快照实操例子 执行效果
-n 显示筛选结果的「行号」 grep -n java.lang.Thread.State dump17 输出包含线程状态的行,同时显示这行在 dump17 里的行号(方便定位)
-i 忽略大小写 grep -i "waiting" dump17 同时筛选 "WAITING""waiting""Waiting"(dump 里一般是大写,但日志里可能有小写)
-v 反向筛选(排除关键词) grep -v "RUNNABLE" dump17 筛选出 "不包含 RUNNABLE" 的线程状态行(只看阻塞 / 等待的线程)
-c 只统计「筛选到的行数」 grep -c java.lang.Thread.State dump17 直接输出 "dump17 里有多少行包含线程状态"(比如输出 398,代表有 398 个线程)

✅ 小技巧:线上排查时,先用「grep -c 关键词 文件名」快速统计数量,再用「grep -n 关键词 文件名」定位具体行,效率翻倍。

第二步:awk ------ 「提取关键信息的小刀」

awk 就像 "一把小刀",核心作用是:把 grep 筛选出来的行,"切" 成多列,只提取我们需要的列

为什么需要它?------ 上一步 grep 输出的行,包含多余的前缀(java.lang.Thread.State:),我们只需要 "RUNNABLE""WAITING (on object monitor)" 这部分,这时候就用 awk 来 "切"。

线程快照里的核心用法
bash 复制代码
awk '{print $2$3$4$5}'
说明
  1. awk 默认会把「一行内容」按「空格」分成多列,列数从1开始计数(1 是第一列,$2 是第二列,以此类推);

  2. 我们先看 grep 输出的行(awk 的输入就是 grep 的输出):

    复制代码
    java.lang.Thread.State: RUNNABLE  # 按空格拆分,$1=java.lang.Thread.State:,$2=RUNNABLE
    java.lang.Thread.State: WAITING (on object monitor)  # $1=前缀,$2=WAITING,$3=(on,$4=object,$5=monitor)
  3. 指令解读:print $2$3$4$5

    • $2:提取第二列(比如 RUNNABLE、WAITING);
    • 34$5:提取第三、四、五列(对应 "(onobjectmonitor)",去掉空格);
    • 连起来写$2$3$4$5:是为了把 "WAITING (on object monitor)" 变成 "WAITING (onobjectmonitor)"(去掉空格,方便后续排序和计数);
  4. 执行结果(结合上一步 grep):

    bash 复制代码
    grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}'

    输出就是线程状态关键词:

    复制代码
    RUNNABLE
    WAITING(onobjectmonitor)
    TIMED_WAITING(sleeping)
  • 不用记复杂的 awk 语法,只记「$ 数字」就是 "提取第 N 列";
  • 为什么不用1?------ 1 是 "java.lang.Thread.State:",是我们不需要的前缀,所以从 $2 开始;
  • 为什么连写2345?------ 因为不同状态的列数不一样(RUNNABLE 只有2,WAITING有2-$5),连写能保证 "不管多少列,都能完整提取状态",不会遗漏。

第三步:sort ------ 「给关键信息排序的工具」(排序)

sort 就像 "整理卡片",核心作用是:把 awk 提取出来的 "线程状态关键词",按字母顺序排序,把相同的状态排在一起

为什么需要它?------ 如果不排序,相同的状态会分散在各处(比如第一个是 WAITING,第二个是 RUNNABLE,第三个又是 WAITING),后续无法统计数量。

线程快照里的核心用法(必记)
bash 复制代码
sort
说明(结合前面两步)
bash 复制代码
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort
执行结果

会把所有线程状态关键词,按字母顺序排好,相同的状态聚在一起:

复制代码
RUNNABLE
RUNNABLE
TIMED_WAITING(onobjectmonitor)
TIMED_WAITING(sleeping)
WAITING(onobjectmonitor)
WAITING(onobjectmonitor)
小补充

sort 默认是「升序排序」(A-Z),如果想降序,加-r参数(比如sort -r),但线程快照统计里,升序就足够用了。

第四步:uniq -c ------ 「统计数量的计数器」(计数)

uniq 就像 "数卡片的计数器",核心作用是:把 sort 排好序的 "相同状态" 去重,同时统计每种状态出现的次数

  • 不加-c:只去重(比如把多个 RUNNABLE 变成 1 个 RUNNABLE);
  • -c:去重 + 计数(这是我们需要的,统计每种状态有多少个线程)。
线程快照里的核心用法
bash 复制代码
uniq -c
拆解说明
bash 复制代码
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort | uniq -c
执行结果
复制代码
39 RUNNABLE
21 TIMED_WAITING(onobjectmonitor)
6 TIMED_WAITING(parking)
51 TIMED_WAITING(sleeping)
305 WAITING(onobjectmonitor)
3 WAITING(parking)
结果解读
  • 前面的数字:每种线程状态的数量(比如 39 代表有 39 个 RUNNABLE 线程,305 代表有 305 个 WAITING 线程);
  • 后面的关键词:线程状态(和 awk 提取的一致)。

✅ 到这里,我们就完成了 "从几百行快照里,1 行指令统计出所有线程状态数量" 的操作 ------ 不用手动翻,不用手动数,精准又高效。


三、这串指令能改成哪些常用场景?

学会了拆解,我们就可以根据自己的需求,改造这串指令,以下 3 个场景是线上排查高频,直接复制可用:

场景 1:统计 dump 文件里,阻塞(BLOCKED)线程的数量

bash 复制代码
grep java.lang.Thread.State dump17 | awk '{print $2$3$4$5}' | sort | uniq -c | grep BLOCKED

在原来的统计结果里,再用 grep 筛选出 "包含 BLOCKED" 的行,直接得到 "BLOCKED 线程有多少个"(比如输出 "5 BLOCKED (onobjectmonitor)",代表有 5 个线程阻塞)。

场景 2:统计日志里,ERROR 出现的次数(通用场景)

bash 复制代码
grep -i "error" app.log | awk '{print $1}' | sort | uniq -c
  • grep -i "error" app.log:筛选出日志里所有包含 error 的行(忽略大小写);
  • awk '{print $1}':提取第一列(一般是日期,比如 2024-05-01);
  • sort | uniq -c:统计每天出现多少个 ERROR,方便定位 "哪一天出了问题"。

场景 3:忽略大小写,统计 dump 里所有等待状态的线程总数

bash 复制代码
grep -i "waiting" dump17 | grep -c java.lang.Thread.State
  • grep -i "waiting" dump17:筛选出所有包含 "等待" 关键词的行;
  • grep -c java.lang.Thread.State:只统计其中 "属于线程状态" 的行,得到等待线程的总数(不用排序,直接计数)。

四、必避的 3 个坑

  1. 坑 1:指令里少写「|」(管道符) → 管道符是 "流水线的传送带",把前一个指令的输出,传给后一个指令,少写一个,指令就会报错(比如 grep 和 awk 之间必须加 |);
  2. 坑 2:awk 的数字写错 → 比如把2 写成1,会提取到"java.lang.Thread.State:"这个前缀,导致后续统计出错,记住:线程状态从2 开始提取;
  3. 坑 3:先 uniq 再 sort → 必须先 sort(把相同状态排在一起),再 uniq -c,否则 uniq 无法正确去重计数(比如先 uniq,相同状态分散在各处,会被当成不同的状态,统计结果不准)。

五、这串指令的完整拆解话术

"你用 grep+awk+sort+uniq -c 统计线程状态,每一步都在做什么?"

"这串指令是一个'筛选→提取→排序→计数'的流水线,每一步的作用如下:

  1. grep:从 dump 文件里,筛选出所有包含线程状态(java.lang.Thread.State)的行,过滤掉无用信息;
  2. awk:把筛选出来的行,按空格拆分,提取出线程状态关键词(从2开始,连写2-$5,避免遗漏状态描述);
  3. sort:把提取到的线程状态关键词按字母排序,把相同的状态聚在一起,为后续计数做准备;
  4. uniq -c:对排序后的状态进行去重,同时统计每种状态出现的次数,最终得到每种线程状态的数量分布,快速定位异常。"

六、小结

今天咱们了解了「grep+awk+sort+uniq -c」这串 "神指令",就是记住 4 个步骤:筛选(grep)→ 提取(awk)→ 排序(sort)→ 计数(uniq -c)。

重点不在于记指令本身,而在于理解 "流水线" 的逻辑 ------ 不管是线程快照、日志分析,只要是 "从大量文本里找关键信息、统计数量",都可以用这个逻辑改造指令。

学会这串指令,你线上排查的效率会翻倍,不用再手动翻几百、几千行文本,1 行指令就能搞定统计,面试时也能轻松拆解,加分不少。

相关推荐
unfeeling_1 小时前
Tomcat实验
java·tomcat
Hx_Ma161 小时前
前台模块以及分页逻辑
java·开发语言
亓才孓1 小时前
AspectJ和SpringAOP的区别
java·开发语言
大鹏说大话1 小时前
破局单体瓶颈:SQLParser 解析器的分层架构重构实战
开发语言
tod1132 小时前
C++ 核心知识点全解析(八)
开发语言·c++·面试经验
Ljwuhe2 小时前
C++类与对象(上)
开发语言·c++
十启树2 小时前
QGis开发环境部署
开发语言·gis·qgis
亚比囧2 小时前
Java基础--面向对象(二)
java·开发语言