线程从runnable切换到blocked的根本原因是争抢已被占用的对象监视器锁(monitor),jvm在执行monitorenter指令时检查锁空闲状态,失败则立即转入blocked并加入entry set等待队列,该过程毫秒级完成、无缓冲、不响应中断,仅当持有线程触发monitorexit后由jvm唤醒。

线程从 RUNNABLE 切换到 BLOCKED,根本原因不是“主动等待”,而是它在尝试获取一个已被占用的对象监视器锁(Monitor Lock)时失败了。这个过程本质是一场资源竞争,JVM 通过内置的锁机制强制让失败者暂停执行,直到锁可用。
为什么必须争抢?因为锁是排他性的
synchronized 关键字背后依赖的是每个 Java 对象关联的监视器(Monitor)。这个监视器只允许一个线程持有——就像一扇只能单人通行的门。当线程 A 正在执行 synchronized(obj) { ... },它就占用了 obj 的监视器;此时线程 B 也想进同一扇门,JVM 就必须让它停在门外(即 BLOCKED 状态),不能插队、不能绕行、也不能强行开门。
- 锁不是“可共享”的信号量,而是互斥凭证
- JVM 不会为线程 B 分配新锁,也不会复制锁,它只认这一个 Monitor 实例
- 争抢不是设计缺陷,而是保证临界区原子性的必要代价
争抢发生在进入 synchronized 的瞬间
状态切换不是在代码里“写出来的”,而是在 JVM 执行字节码指令 monitorenter 时实时判定的:
- 线程执行到
synchronized块入口,触发monitorenter - JVM 检查目标对象的 Monitor 是否空闲(即 owner 字段是否为 null)
- 若空闲 → 获取成功 → 继续执行 → 保持 RUNNABLE
- 若被占 → 立即转入 BLOCKED 状态,并被加入该对象的 Entry Set(等待队列)
这个判断毫秒级完成,没有中间态,也没有“稍等一下再试”的缓冲——失败即阻塞。
BLOCKED 不等于“没在干活”,只是“卡在锁门口”
处于 BLOCKED 的线程仍属于 JVM 管理的活跃线程,它不消耗 CPU,但占用内存和线程栈;它不参与调度竞争,也不响应中断(interrupt() 对 BLOCKED 线程无效);它唯一能做的,就是等持有锁的线程退出同步块(触发 monitorexit),JVM 自动唤醒队列中一个线程重新尝试获取锁。
- 唤醒顺序不保证 FIFO,取决于 JVM 实现(通常偏向公平性但非绝对)
- 如果多个线程 BLOCKED 在不同对象上,彼此无影响;只有争同一把锁才形成真实竞争链
- 用
jstack查看线程堆栈时,BLOCKED 线程会明确标注 “waiting to lock ”
类锁和实例锁都遵循同一套争抢规则
不管是 synchronized(instanceMethod) 还是 synchronized(staticMethod),底层都是对某个对象加 Monitor 锁:
- 实例方法 → 锁当前
this对象 - 静态方法 → 锁当前类的
Class对象(如MyClass.class) - 显式
synchronized(obj)→ 锁传入的任意对象引用
只要多个线程瞄准的是同一个锁对象,就会触发争抢 → 可能 BLOCKED。这也是为什么静态同步方法容易成为全局瓶颈:所有实例调用它,实际都在抢同一把 Class 锁。










