
Java线程的BLOCKED和WAITING状态到底对应什么CPU调度行为
Java里的BLOCKED和WAITING(含TIMED_WAITING)都不是“挂起”——它们是JVM层面的状态映射,背后对应的OS线程状态可能都是SLEEPING或UNINTERRUPTIBLE_SLEEP,但触发原因完全不同。
关键区别不在CPU是否运行,而在线程被阻塞的**资源类型**和**唤醒机制**:
-
BLOCKED:在等monitor锁(synchronized入口),由JVM锁竞争逻辑管理,唤醒依赖锁释放+竞争胜出 -
WAITING:调用了Object.wait()、Thread.join()、LockSupport.park()等,等待显式通知(notify()、unpark())或目标线程终止 - 两者都不会消耗CPU,但
BLOCKED线程仍处于“就绪队列候补位”,而WAITING线程已脱离调度器的常规调度循环
为什么jstack里看到WAITING却查不到锁信息
jstack输出中的java.lang.Thread.State: WAITING (parking)常让人误以为和锁有关,其实parking只是LockSupport.park()的提示文本,和monitor无关。
常见误解来源:
立即学习“Java免费学习笔记(深入)”;
- 混淆
synchronized和java.util.concurrent底层实现:AQS用park/unpark实现阻塞,不走monitor路径 - 误读线程栈:看到
Unsafe.park就以为是系统级挂起,实际它只是调用OS的futex或condition wait,且可被中断 -
WAITING线程不会出现在jstack的“locked synchronizers”区块里——那块只统计monitor和java.util.concurrent显式锁(如ReentrantLock)
Thread.sleep()和Object.wait()在调度层面有本质差异吗
没有。从OS角度看,两者最终都让线程进入睡眠态(例如Linux的TASK_INTERRUPTIBLE),差别全在JVM语义层:
-
Thread.sleep(1000):纯时间等待,到期自动唤醒,不释放任何锁(哪怕在synchronized块内也照睡不误) -
Object.wait(1000):必须持有对象锁才能调用,调用时**立即释放该锁**,唤醒后需重新竞争锁才能继续执行 - 性能上无显著差异,但
wait/notify涉及锁重入和条件检查,开销略高;sleep更轻量,但无法响应条件变化
排查线程卡死时,别只盯着BLOCKED状态
线上出现“请求没响应”,jstack里满屏WAITING比BLOCKED更危险——因为BLOCKED至少说明有线程在干活、只是争锁激烈;而大量WAITING往往意味着条件未满足、通知丢失或park后忘了unpark。
- 重点检查:
LockSupport.park()调用点是否配对unpark();wait()前是否有while循环校验条件;CountDownLatch.await()是否被重复countDown导致提前返回 - 注意
WAITING线程可能因InterruptedException被唤醒但未处理,之后逻辑卡在后续判断分支里(比如忘记重试) -
Thread.getState()返回WAITING不代表线程真在等——如果它刚被interrupt,状态仍是WAITING,但下一行就抛异常退出了
最麻烦的情况是线程在native方法里阻塞(比如IO或JNI),此时jstack显示RUNNABLE,但实际已交出CPU——这种才真正算“挂起”,而Java线程状态模型根本不描述它。











