这个问题问得很关键 👍,很多同学习惯上都把 WAITING 和 BLOCKED 混在一起,其实它们在 Java 线程状态里有明确区别。
🔑 线程的主要状态
Java 线程的状态枚举 Thread.State
有:
- NEW
- RUNNABLE
- BLOCKED
- WAITING
- TIMED_WAITING
- TERMINATED
1️⃣ BLOCKED(阻塞状态)
-
原因 :线程在等待 进入 synchronized 临界区 ,但是 锁被别人占用。
-
特点:
- 线程没有执行机会,只能等锁释放。
- 一旦锁释放,JVM 会唤醒一个等待锁的线程,它会进入 RUNNABLE。
-
例子:
javasynchronized (lock) { // t1 进入临界区 synchronized (lock) { // t2 在这里会进入 BLOCKED,因为 t1 占有 lock } }
2️⃣ WAITING(等待状态)
-
原因 :线程主动调用了
Object.wait()
、Thread.join()
或LockSupport.park()
,表示"我等别人来唤醒"。 -
特点:
- 线程已经放弃 CPU 和锁(如果是 wait)。
- 必须等待其他线程显式唤醒它(如
notify()
、notifyAll()
、unpark()
)。
-
例子:
javasynchronized (lock) { lock.wait(); // t1 调用后进入 WAITING 状态,等别人 notify }
3️⃣ 核心区别
状态 | 触发条件 | 唤醒条件 | 是否持有锁 |
---|---|---|---|
BLOCKED | 等待进入 synchronized,但锁被占用 | 目标锁被释放,JVM 让它去竞争 | 没有 |
WAITING | 调用 wait()/join()/park() |
其他线程调用 notify()/notifyAll()/unpark() |
没有(wait 会释放锁) |
4️⃣ 再举个直观例子
java
Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("t1 获取锁后 wait");
lock.wait(); // t1 -> WAITING
} catch (InterruptedException ignored) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
System.out.println("t2 获取锁");
try { Thread.sleep(5000); } catch (InterruptedException ignored) {}
}
});
Thread t3 = new Thread(() -> {
synchronized (lock) {
System.out.println("t3 也想获取锁 -> BLOCKED (直到 t2 释放锁)");
}
});
t1.start();
t2.start();
t3.start();
t1
调用wait()
→ 进入 WAITING(在 lock 的等待队列里)。t2
获取到锁后睡眠 →t3
想进 synchronized,进不去 → 进入 BLOCKED(在 lock 的锁池里)。
✅ 总结一句
- BLOCKED:卡在 synchronized 门口,等别人释放锁。
- WAITING:自己主动放弃执行,等别人"通知"我继续。
那么BLOCKED的线程也被挂在在对象锁的 monitor的队列中吗?和对象锁的 monitor的WAITING队列是两个不同的队列?还是只有一个WAITING队列,BLOCKED线程在使用CAS一直监测锁?
好问题 👍,你已经触及 Java 对象锁 monitor
的内部机制了。
答案是:确实有不同的队列,不是同一个。
🔑 Monitor 的两个重要队列
在 HotSpot JVM 里,一个对象的 Monitor(也就是对象锁)里通常维护两个主要队列:
-
Entry List(入口队列 / 竞争锁的队列)
- 线程尝试进入
synchronized(obj)
时,如果锁被占用,就会进入 BLOCKED 状态。 - 这些线程被挂在
Entry List
中(或者叫 Contention List,依 JVM 实现而不同)。 - 一旦锁释放,JVM 会从这个队列里挑选一个线程唤醒,让它去竞争锁。
- 线程尝试进入
-
Wait Set(等待队列)
- 线程在持有锁时调用
obj.wait()
→ 会释放锁,然后进入 WAITING/TIMED_WAITING 状态。 - 这些线程被挂在
Wait Set
中。 - 必须等待其他线程调用
obj.notify()
/obj.notifyAll()
才能被唤醒。 - 被唤醒后,线程不会直接运行,而是回到 Entry List 再次竞争锁。
- 线程在持有锁时调用
✨ 关键区别
状态 | 队列 | 进入条件 | 唤醒条件 |
---|---|---|---|
BLOCKED | Entry List | 想进入 synchronized,但锁被别人占着 | 锁释放,JVM 选一个线程进入 RUNNABLE |
WAITING | Wait Set | 已经拿到锁,但调用了 wait() |
其他线程调用 notify()/notifyAll() ,然后回到 Entry List 去竞争锁 |
🔍 回答你的问题
BLOCKED 的线程也被挂在对象锁的 monitor 的队列中吗?
✅ 是的,但它们挂在 Entry List(锁竞争队列) 。
而调用 wait()
的线程挂在 Wait Set(等待队列)。
是两个不同的队列吗?
✅ 对!BLOCKED
和 WAITING
分属两个不同队列:
BLOCKED
→ Entry List(等锁)WAITING
→ Wait Set(等通知)
还是只有一个 WAITING 队列,BLOCKED 线程在用 CAS 一直监测锁?
❌ 不是用 CAS 自旋监测。
Java 的 synchronized
是 重量级锁时走操作系统的阻塞挂起机制 ,BLOCKED 的线程会被 挂起,放进 Entry List,不会浪费 CPU 自旋(除非是自适应自旋锁阶段,短时间内会 CAS 自旋一会儿,但长期阻塞时会进入 Entry List)。
🖼 图示(简化版)
┌───────────┐
│ Monitor │
└───────────┘
▲ ▲
Entry List Wait Set
(BLOCKED) (WAITING)
- 想进
synchronized
→ 进 Entry List,BLOCKED。 - 调用
wait()
→ 进 Wait Set,WAITING。 notify()
从 Wait Set 捞出来 → 扔回 Entry List,等锁。
✅ 总结一句
-
BLOCKED 和 WAITING 都挂在对象锁
monitor
内部,但在 不同的队列:- BLOCKED → Entry List(等锁)
- WAITING → Wait Set(等通知 + 之后还得等锁)
那么,调用了notify()All函数,如果此时,也有被阻塞在synchronized()阶段的线程,那么,最终是Wait set中的线程还是Entry List线程拿到锁(我知道Wait set中的线程会全部挂到Entry List中),这个是不确定的?是吗?
👌你问到了 notifyAll() 后 Wait Set 线程和 Entry List 线程竞争锁的优先级问题 。
答案是:不确定,取决于 JVM 实现,Java 规范里没有保证顺序。
🔑 notifyAll() 的执行流程
假设有两类线程:
- BLOCKED 线程 :卡在
synchronized(obj)
门口,等锁,挂在 Entry List。 - WAITING 线程 :调用
obj.wait()
后进入 Wait Set,等通知。
此时某个线程持有 obj
的锁,并在同步代码块里调用了 obj.notifyAll()
:
- 所有 Wait Set 里的线程 被移动到 Entry List。
- 当前持有锁的线程继续执行,直到退出
synchronized(obj)
,释放锁。 - 当锁被释放时,Entry List 里所有线程(包含原来就有的 BLOCKED 和刚被 notifyAll 唤醒的 WAITING)一起参与竞争。
🔍 谁能拿到锁?
-
JVM 没有规定调度顺序,所以可能是:
- 原来就在 Entry List 的 BLOCKED 线程抢到锁
- 刚从 Wait Set 转移过来的线程抢到锁
-
这完全取决于 JVM 的调度策略(通常是 非公平竞争,也就是谁先被操作系统调度到,谁就能 CAS 成功拿到锁)。
✨ 举个例子
java
class Example {
private final Object lock = new Object();
public void test() {
Thread t1 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("t1 wait");
lock.wait(); // 进入 Wait Set
System.out.println("t1 被唤醒,准备竞争锁");
} catch (InterruptedException ignored) {}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
try {
System.out.println("t2 wait");
lock.wait(); // 进入 Wait Set
System.out.println("t2 被唤醒,准备竞争锁");
} catch (InterruptedException ignored) {}
}
});
Thread notifier = new Thread(() -> {
synchronized (lock) {
System.out.println("notifier notifyAll");
lock.notifyAll(); // 唤醒所有等待中的线程
}
});
Thread blocker = new Thread(() -> {
synchronized (lock) {
System.out.println("blocker 占有锁 3s");
try { Thread.sleep(3000); } catch (InterruptedException ignored) {}
}
});
t1.start();
t2.start();
blocker.start();
try { Thread.sleep(1000); } catch (InterruptedException ignored) {}
notifier.start();
}
}
t1
、t2
→ 进入 Wait Set。blocker
→ 持有锁很久,此时 Entry List 里可能也有别的线程想拿锁。notifier
调用notifyAll()
,唤醒t1
、t2
→ 它们转入 Entry List。- 当
blocker
释放锁时,Entry List 里所有线程(包括老的 BLOCKED 和新唤醒的t1/t2
)会 一起竞争,结果不确定。
✅ 总结
notifyAll()
会把 Wait Set 的线程全部转移到 Entry List。- Entry List + 新来的线程 → 一起竞争锁。
- Java 规范没要求谁优先,JVM 通常是非公平调度,所以 最终谁拿到锁是不确定的。