ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

线程阻塞分析:Object.wait 耗时真相

线程阻塞分析: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()期间:

  • 线程状态从RUNNABLEWAITING/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_WAITINGTIMED_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/notifyCountDownLatchFuture


六、如何进一步定位

步骤 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 ▌20ms

System 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 → 看线程状态色块更准确

九、总结要点

  1. Object.wait/Thread.sleep的 Self Time 大 ≠ 性能问题
  2. 本质是"挂钟时间",不是"CPU 时间",线程在挂起状态
  3. 要看是谁在 wait:
    • 空闲的线程池 Worker → 正常
    • 主线程 → 严重问题
    • 显式业务 wait → 具体分析
  4. 推荐用 System Trace(Perfetto)而不是采样 Profiler
  5. 真正的 CPU 热点一般是循环、序列化、加解密、算法等,不会是 wait

返回列表