线程从runnable切换到blocked的唯一核心原因是争抢已被占用的对象监视器锁(monitor),即执行synchronized时因锁被持有而立即阻塞并加入entry set,该过程毫秒级完成、不响应中断、由jvm自动唤醒。

Java 中线程从运行(RUNNABLE)到阻塞(BLOCKED)的切换,核心在于锁竞争——不是因为 CPU 时间用完、也不是主动休眠或等待通知,而是线程在尝试进入 synchronized 代码块或方法时,发现目标对象的监视器锁(monitor)已被其他线程持有。
触发 BLOCKED 的典型场景
只有以下一种情况会直接导致线程从 RUNNABLE 进入 BLOCKED 状态:
- 线程执行到
synchronized(obj) { ... }或调用synchronized实例/静态方法时,检查对象头的 Mark Word,确认锁正被别的线程占用 - 此时 JVM 不会让它自旋或重试,而是立即将其状态设为
BLOCKED,并把它加入该对象监视器的 EntryList(锁等待队列) - 该线程不再参与 CPU 调度,不消耗 CPU 时间,只等待锁释放后的唤醒机会
RUNNABLE 和 BLOCKED 的本质区别
RUNNABLE 并不等于“正在 CPU 上运行”,它是 JVM 定义的广义可运行状态,包含两种子情形:
你接手一个需求时,是不是经常这样:"改一个小字段 → 结果把不相关的代码也改了"、"需求说得很模糊 → 做着做着发现跑偏了"、"数据库加个字段 → 上线才发现漏了数据兼容"、"做了个新功能 → 一个月后自己都忘了当时怎么设计的"、"写到一半被中断 → 再回来不知道从哪继续";这个 Skill 就是专门解决这些问题的。 它会自动探测你的项目环境(JDK、Spring Boot、ORM、数据库、前端框架),记在
- 真正占用 CPU 执行(OS 层面的 running)
- 已就绪、等待调度(OS 层面的 ready),比如刚被唤醒、刚分配完资源、刚从 I/O 返回等
而 BLOCKED 是 JVM 明确标记的“因锁挂起”状态,它和 WAITING/TIMED_WAITING 的关键差异在于:
— BLOCKED 线程无需被显式唤醒;
— 它只依赖锁释放事件,由 JVM 监视器自动将其从 EntryList 移出、重新置为 RUNNABLE。
如何验证和观察这个过程
可通过 Thread.getState() 或线程 dump(如 jstack)实时查看:
- 在多线程竞争同一把锁的代码中,启动后稍作延迟,打印线程状态,常看到一个线程是 RUNNABLE,另一个是 BLOCKED
- 用
jstack <pid></pid>查看堆栈,BLOCKED 线程会明确标注:"java.lang.Thread.State: BLOCKED (on object monitor),并指出等待的是哪个对象的锁 - 注意:调用
wait()进入的是 WAITING,调用sleep()进入的是 TIMED_WAITING,它们都不属于 BLOCKED
常见误区澄清
这些操作让线程变成 BLOCKED:
-
Thread.sleep()→ 进入 TIMED_WAITING -
Object.wait()→ 进入 WAITING(需先持有锁,再释放并等待) - I/O 阻塞(如 socket.read())→ 在 JVM 层仍显示 RUNNABLE(底层由操作系统线程挂起,JVM 无法感知,故不改变 Java 线程状态)
-
Thread.yield()或时间片耗尽 → 回到 RUNNABLE(就绪态),不是 BLOCKED
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南










