前置知识:Java 线程的核心状态

先明确基础:Java 线程的状态定义在 Thread.State 枚举中,核心状态有 6 种,其中和 “等待 / 阻塞” 相关的是 3 种:

  • BLOCKED:阻塞(被动等锁)
  • WAITING:无限等待(主动等事件,需显式唤醒)
  • TIMED_WAITING:限时等待(WAITING 的超时版本,如 sleep()wait(long)

我们重点对比 BLOCKEDWAITING,先分别拆解,再总结区别。


一、BLOCKED(阻塞状态)

1. 核心定义

线程被动进入的状态:当线程尝试获取 synchronized 同步锁(对象监视器锁) 失败(锁已被其他线程持有),就会进入 BLOCKED 状态,直到锁被释放后参与竞争。

2. 唯一触发场景

只有一种情况会进入 BLOCKED 状态:

线程试图进入 synchronized 修饰的方法 / 代码块,但该锁的所有权已被其他线程占用。

3. 关键特性

  • 被动等待:线程不是主动 “选择” 等待,而是抢锁失败后的被动状态;
  • 等待的目标:等待的是 “可用的 synchronized 锁”(锁被释放就有机会抢);
  • 是否释放锁:本身就没拿到锁,不存在 “释放锁” 的说法;
  • 唤醒方式:无需显式唤醒 —— 当持有锁的线程释放锁(退出同步块 / 方法),JVM 会自动唤醒所有争抢该锁的 BLOCKED 线程,让它们重新竞争锁;
  • 和锁强绑定:只和 synchronized 锁的竞争相关,与 wait() 无关。

4. 代码示例(BLOCKED 状态演示)

这个示例就是你之前看的 sleep() 不释放锁的场景,线程 2 抢锁失败进入 BLOCKED 状态:

public class BlockedStateDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) {
        // 线程1:获取锁后sleep(不释放锁)
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("线程1:获取锁,开始sleep 3秒(不释放锁)");
                try {
                    Thread.sleep(3000); // sleep期间锁仍被持有
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程1:sleep结束,释放锁");
            }
        }, "线程1").start();

        // 线程2:尝试抢锁,失败后进入BLOCKED状态
        new Thread(() -> {
            System.out.println("线程2:尝试获取锁...");
            synchronized (lock) { // 抢锁失败,进入BLOCKED
                System.out.println("线程2:成功获取锁(BLOCKED状态结束)");
            }
        }, "线程2").start();
    }
}

状态变化

  • 线程 1:RUNNABLE → TIMED_WAITING(sleep)→ RUNNABLE → TERMINATED;
  • 线程 2:RUNNABLE → BLOCKED(抢锁失败)→ 线程 1 释放锁后 → RUNNABLE(抢到锁)→ TERMINATED。

二、WAITING(无限等待状态)

1. 核心定义

线程主动进入的状态:线程通过调用特定方法主动放弃执行权,进入 “无时限等待”,必须通过显式唤醒才能回到 RUNNABLE 状态,否则会永久等待。

2. 触发场景(仅以下 3 种)

只有调用以下方法(无参版本)才会进入 WAITING 状态:

方法 场景说明
Object.wait() 释放对象锁,等待同一对象的 notify()/notifyAll() 唤醒
Thread.join() 等待目标线程完全结束(比如 t.join() 表示当前线程等 t 执行完)
LockSupport.park() 底层等待方法,需通过 LockSupport.unpark(线程) 显式唤醒

3. 关键特性

  • 主动等待:线程是主动调用 wait()/join() 等方法进入等待,而非被动抢锁失败;
  • 等待的目标:等待的是 “特定事件”(如被 notify() 唤醒、目标线程结束),而非锁;
  • 是否释放锁
    • 调用 Object.wait()会释放持有的对象锁(这是核心!);
    • 调用 Thread.join():不会释放锁(join 本质是 wait (),但锁是目标线程对象);
    • 调用 LockSupport.park():不涉及 synchronized 锁,无需释放;
  • 唤醒方式:必须显式唤醒,否则永久等待:
    • wait() → 需 notify()/notifyAll()
    • join() → 目标线程结束自动唤醒;
    • park() → 需 unpark(线程)
  • 和事件强绑定:等待的是 “事件发生”,而非锁的释放。

4. 代码示例(WAITING 状态演示)

这个示例是你之前学的 wait() 场景,线程调用 lock.wait() 后进入 WAITING 状态:

public class WaitingStateDemo {
    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread waitThread = new Thread(() -> {
            synchronized (lock) {
                try {
                    System.out.println("等待线程:获取锁,调用wait()进入WAITING状态");
                    lock.wait(); // 释放锁,进入WAITING(需notify唤醒)
                    System.out.println("等待线程:被唤醒,退出WAITING状态");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "等待线程");
        waitThread.start();

        // 主线程等待1秒,确保waitThread进入WAITING状态
        Thread.sleep(1000);
        System.out.println("等待线程的状态:" + waitThread.getState()); // 输出 WAITING

        // 唤醒线程:调用notify()唤醒等待线程
        new Thread(() -> {
            synchronized (lock) {
                System.out.println("唤醒线程:获取锁,调用notify()");
                lock.notify(); // 唤醒WAITING的线程
            }
        }, "唤醒线程").start();
    }
}

状态变化

  • 等待线程:RUNNABLE → WAITING(调用 wait())→ 被 notify 后 → BLOCKED(抢锁)→ RUNNABLE → TERMINATED;
  • 注意:唤醒后不会直接回到 RUNNABLE,而是先进入 BLOCKED 抢锁,抢到后才执行!

三、补充:TIMED_WAITING(限时等待)

你可能会混淆这个状态,它是 WAITING 的 “超时版本”

  • 触发场景:调用带超时参数的方法,如 Thread.sleep(long)Object.wait(long)Thread.join(long)LockSupport.parkNanos()
  • 核心区别:无需显式唤醒,超时后自动回到 RUNNABLE 状态;
  • 示例:Thread.sleep(3000) 会让线程进入 TIMED_WAITING 状态,3 秒后自动醒。

四、BLOCKED vs WAITING 核心对比表

维度 BLOCKED(阻塞) WAITING(无限等待)
触发原因 抢 synchronized 锁失败(被动) 主动调用 wait()/join()/park()(主动)
等待的目标 等待 “synchronized 锁释放” 等待 “特定事件”(notify、目标线程结束等)
是否释放锁 未拿到锁,无锁可释放 调用 wait() 会释放锁,join()/park() 不释放
唤醒方式 锁释放后自动竞争(无需显式唤醒) 必须显式唤醒(如 notify ()、unpark ())
典型触发方法 进入 synchronized 块 / 方法(抢锁失败) Object.wait()(无参)、Thread.join()(无参)、LockSupport.park()
状态转换 BLOCKED → 抢锁成功 → RUNNABLE WAITING → 被唤醒 → BLOCKED(抢锁)→ RUNNABLE

五、高频易错点

  1. “wait () 后进入 BLOCKED” 是错误的wait() 后线程进入 WAITING,唤醒后先进入 BLOCKED 抢锁,抢到才到 RUNNABLE;
  2. BLOCKED 只和 synchronized 有关:Lock 锁(如 ReentrantLock)的等待线程不会进入 BLOCKED,而是进入 WAITING/TIMED_WAITING;
  3. sleep () 进入 TIMED_WAITING,不是 BLOCKED:新手常误以为 sleep 会让线程阻塞,实际是限时等待。

总结

  1. BLOCKED 是 “被动等锁”:抢 synchronized 锁失败后的状态,等锁释放就自动竞争,无需显式唤醒;
  2. WAITING 是 “主动等事件”:线程主动调用方法进入的状态,必须通过 notify ()/unpark () 等显式唤醒,否则永久等待;
  3. 核心记忆:BLOCKED 等 “锁”,WAITING 等 “事件”,TIMED_WAITING 是 WAITING 的超时版。
Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐