java线程状态中,WAITING和BLOCKED区别是什么?

这个问题问得很关键 👍,很多同学习惯上都把 WAITINGBLOCKED 混在一起,其实它们在 Java 线程状态里有明确区别。


🔑 线程的主要状态

Java 线程的状态枚举 Thread.State 有:

  • NEW
  • RUNNABLE
  • BLOCKED
  • WAITING
  • TIMED_WAITING
  • TERMINATED

1️⃣ BLOCKED(阻塞状态)

  • 原因 :线程在等待 进入 synchronized 临界区 ,但是 锁被别人占用

  • 特点

    • 线程没有执行机会,只能等锁释放。
    • 一旦锁释放,JVM 会唤醒一个等待锁的线程,它会进入 RUNNABLE。
  • 例子

    java 复制代码
    synchronized (lock) {
        // t1 进入临界区
        synchronized (lock) {
            // t2 在这里会进入 BLOCKED,因为 t1 占有 lock
        }
    }

2️⃣ WAITING(等待状态)

  • 原因 :线程主动调用了 Object.wait()Thread.join()LockSupport.park(),表示"我等别人来唤醒"。

  • 特点

    • 线程已经放弃 CPU 和锁(如果是 wait)。
    • 必须等待其他线程显式唤醒它(如 notify()notifyAll()unpark())。
  • 例子

    java 复制代码
    synchronized (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(也就是对象锁)里通常维护两个主要队列:

  1. Entry List(入口队列 / 竞争锁的队列)

    • 线程尝试进入 synchronized(obj) 时,如果锁被占用,就会进入 BLOCKED 状态。
    • 这些线程被挂在 Entry List 中(或者叫 Contention List,依 JVM 实现而不同)。
    • 一旦锁释放,JVM 会从这个队列里挑选一个线程唤醒,让它去竞争锁。
  2. 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(等待队列)

是两个不同的队列吗?

✅ 对!BLOCKEDWAITING 分属两个不同队列:

  • 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,等锁。

总结一句

  • BLOCKEDWAITING 都挂在对象锁 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()

  1. 所有 Wait Set 里的线程 被移动到 Entry List
  2. 当前持有锁的线程继续执行,直到退出 synchronized(obj),释放锁。
  3. 当锁被释放时,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();
    }
}
  • t1t2 → 进入 Wait Set。
  • blocker → 持有锁很久,此时 Entry List 里可能也有别的线程想拿锁。
  • notifier 调用 notifyAll(),唤醒 t1t2 → 它们转入 Entry List。
  • blocker 释放锁时,Entry List 里所有线程(包括老的 BLOCKED 和新唤醒的 t1/t2)会 一起竞争,结果不确定。

✅ 总结

  • notifyAll() 会把 Wait Set 的线程全部转移到 Entry List。
  • Entry List + 新来的线程 → 一起竞争锁
  • Java 规范没要求谁优先,JVM 通常是非公平调度,所以 最终谁拿到锁是不确定的