线程阻塞分析:Object.wait 耗时真相
一、这个火焰图/调用栈告诉我们什么
com.example.Worker.run Self=3000ms └─ java.lang.Object.wait Self=2980ms ⬅ 大头在这表面现象:Worker.run方法执行了 3 秒,其中2980ms 消耗在Object.wait()上。
关键结论:这不是性能问题,是线程在"等待"。
二、Self Time 是什么(重要前提)
在性能分析工具(如 Android Studio Profiler、Perfetto、Async Profiler)中:
| 概念 | 含义 |
|---|---|
| Total Time | 该方法及其所有子方法的总耗时 |
| Self Time | 只在这个方法自身代码里花的时间(不含子调用) |
举例:
foo() Total=100ms, Self=10ms └─ bar() Total=90ms, Self=90ms说明foo主要在调用bar,自己只干了 10ms 的活。
回到场景:Object.wait的 Self=2980ms 意味着线程卡在wait()这一行 2.98 秒。
三、这不是"CPU 忙",而是"线程阻塞"
关键区分:CPU 时间 vs Wall Clock 时间
| 类型 | 含义 | 举例 |
|---|---|---|
| CPU Time(On-CPU) | 线程真正占用 CPU 执行指令的时间 | 计算、循环 |
| Wall Time(Off-CPU) | 挂钟时间,包含等待 | wait/sleep/IO/锁 |
Object.wait()期间:
- 线程状态从
RUNNABLE→WAITING/TIMED_WAITING - 线程被挂起,让出 CPU
- CPU 占用率是 0
- 但挂钟时间在流逝
使用工具时的陷阱
如果你的 Profiler 是"Sample"模式(采样): → 显示的是 Wall Clock 时间 → 会看到 wait 的耗时 如果是"CPU"模式(只算 On-CPU): → wait/sleep 根本不会出现 → 显示"没在干活"判断你看到的是哪种:如果 wait/sleep 出现在火焰图里且 Self 很大 → 这是Wall Clock / Instrumentation模式。
四、Object.wait()与Thread.sleep()的区别
虽然都表现为"线程不干活",但两者本质不同:
| 特性 | Object.wait() | Thread.sleep() |
|---|---|---|
| 释放锁 | ✅ 释放当前持有的 monitor | ❌ 不释放锁 |
| 唤醒方式 | 被notify()/notifyAll()唤醒 | 到时间自动唤醒 |
| 必须在同步块内 | ✅ 是 | ❌ 否 |
| 线程状态 | WAITING / TIMED_WAITING | TIMED_WAITING |
| 用途 | 线程间协作(生产者-消费者) | 定时延迟 |
| 可被 interrupt | ✅ 是 | ✅ 是 |
代码对比:
// wait - 释放锁,等待通知synchronized(lock){while(!condition){lock.wait();// 阻塞在这里,锁被释放}}// sleep - 不释放锁,单纯延迟Thread.sleep(3000);// 阻塞 3 秒五、如何判断"该不该关心"这个 Self Time
✅ 正常情况(不用管)
场景 A:线程池的 Worker 空闲等待任务
publicvoidrun(){while(running){Tasktask=queue.take();// 底层是 waittask.execute();}}👉 空闲 Worker 卡在take()(内部 wait)是正常的,说明线程池够用。
场景 B:HandlerThread 消息循环
Looper.loop();└─MessageQueue.next()└─ nativePollOnce// 等消息👉 Handler 线程没消息时挂起,这就是设计。
场景 C:主线程 idle
android.os.MessageQueue.nativePollOnceSelf=大量👉 主线程没事干时等待事件,正常状态。
❌ 有问题的情况(需要排查)
问题 1:主线程被 wait 卡住 → ANR 元凶
main thread └─ MyActivity.onClick └─ Object.wait Self=5000ms ⚠️👉 主线程等 5 秒 → 用户点击后界面卡死 →必然 ANR
问题 2:关键业务线程被 sleep 拖慢
publicvoidloadData(){for(inti=0;i<100;i++){Thread.sleep(100);// 无脑轮询 → 累计 10 秒if(checkReady())return;}}👉 应该改成事件通知 / CountDownLatch
问题 3:锁竞争严重,大量线程 wait
30 个线程同时: └─ SharedResource.doWork └─ Object.wait ⚠️ (等 monitor)👉 锁粒度太粗,需要拆分锁或用无锁结构
问题 4:错误的线程通信模式
// 忙等待(反模式)while(!done){Thread.sleep(10);// 轮询}👉 应该用wait/notify、CountDownLatch、Future
六、如何进一步定位
步骤 1:确认是哪种线程
# 抓 Java 线程栈,看 Worker 线程是什么角色adb shell"kill -3$(pidof com.example.app)"adb pull /data/anr/traces.txt# 或者用 jstack (需要 JDK 工具链)jstack<pid>看栈的顶部:
"Worker-1" prio=5 tid=0x... nid=0x... in Object.wait() java.lang.Thread.State: WAITING (on object monitor) at java.lang.Object.wait(Native Method) - waiting on <0x...> (a java.util.LinkedList) at java.lang.Object.wait(Object.java:502) at com.example.Queue.take(Queue.java:42) ⬅ 谁在调 wait at com.example.Worker.run(Worker.java:20)步骤 2:看是谁调用的 wait
- 如果是BlockingQueue.take() / poll()→线程池空闲,正常
- 如果是业务代码里的 wait→ 检查是否合理
- 如果是框架内部(Looper/Handler)→ 正常
步骤 3:切换 Profiler 模式
Android Studio Profiler 三种模式:
| 模式 | 是否包含 wait/sleep | 何时用 |
|---|---|---|
| Sample Java Methods(采样) | ✅ 会显示 wait | 找卡顿(Wall Time) |
| Trace Java Methods(埋点) | ✅ 会显示 wait | 精确调用链 |
| Sample C/C++ Functions | ❌ 只看 CPU | 找 CPU 热点 |
| System Trace(Perfetto) | 用线程状态色块显示 | 最推荐 |
👉想找真正的性能问题,用 System Trace,它会区分:
- 绿色= Running(占用 CPU)
- 蓝色= Runnable(等 CPU 调度)
- 橙色= Sleeping(wait/sleep)
- 红色= Uninterruptible sleep(IO 等)
只有绿色和蓝色才是"CPU 相关问题",橙色的 wait 是正常挂起。
七、可视化对比
火焰图看到的(Wall Time 视角)
Worker.run ████████████████████████ 3000ms └─ wait ████████████████████████ 2980ms ← 看起来很吓人 └─ work ▌20msSystem Trace 看到的(真实占用)
CPU 时间轴: Worker ─░░░░░░░░░░░░░░░░░░░░░░░█─ └─ 睡眠(2980ms) └─ 工作(20ms) └─ CPU 占用 = 0 └─ CPU 占用 = 100%结论:这个线程99.3% 的时间在睡觉,只有 20ms 真正在工作,CPU 是被别人用了。
八、实战决策树
看到 Object.wait / Thread.sleep 大 Self Time │ ├─ 是哪个线程? │ ├─ 主线程 → ⚠️ 严重问题,必查 │ ├─ 关键业务线程 → ⚠️ 检查是否合理 │ └─ 线程池 Worker / Handler 线程 → ✅ 通常正常 │ ├─ 调用者是谁? │ ├─ BlockingQueue.take/poll → ✅ 空闲等任务,正常 │ ├─ Looper.loop / nativePollOnce → ✅ 消息循环,正常 │ ├─ CountDownLatch.await → 🤔 检查发送端 │ ├─ 业务代码显式 wait/sleep → ⚠️ 检查逻辑 │ └─ 锁竞争(waiting on monitor) → ⚠️ 优化锁 │ └─ Profiler 是什么模式? ├─ Sample/Trace → 显示 Wall Time,wait 出现正常 └─ System Trace → 看线程状态色块更准确九、总结要点
Object.wait/Thread.sleep的 Self Time 大 ≠ 性能问题- 本质是"挂钟时间",不是"CPU 时间",线程在挂起状态
- 要看是谁在 wait:
- 空闲的线程池 Worker → 正常
- 主线程 → 严重问题
- 显式业务 wait → 具体分析
- 推荐用 System Trace(Perfetto)而不是采样 Profiler
- 真正的 CPU 热点一般是循环、序列化、加解密、算法等,不会是 wait