Skip to content

等待状态和阻塞状态的差异

约 1038 字大约 3 分钟

多线程与并发美团

2025-03-26

⭐ 题目日期:

美团 - 2024/12/23

📝 题解:

在操作系统中,线程或进程的状态管理是理解并发和资源调度的核心。**等待状态(Waiting)阻塞状态(Blocked)**虽然都表示线程或进程暂时无法执行,但它们的触发条件、行为及底层机制存在显著差异。以下是两者的详细对比:


1. 定义与触发条件

状态触发条件典型场景
阻塞状态线程因外部资源不可用(如I/O未完成、锁被占用等)而暂停执行。- 尝试获取已被其他线程持有的锁(如synchronized)。
- 等待磁盘I/O完成。
等待状态线程主动放弃CPU并等待某个事件触发(如条件变量、定时唤醒或信号通知)。- 调用Object.wait()等待通知。
- 调用Thread.join()等待其他线程结束。

2. 核心差异

维度阻塞状态(Blocked)等待状态(Waiting)
触发方式被动:由系统或外部资源限制导致。主动:通过代码显式触发(如调用wait()join())。
恢复条件等待的资源变为可用(如锁释放、I/O完成)。等待的事件被触发(如收到notify()信号、超时或线程终止)。
状态转换由操作系统内核管理,自动恢复。需依赖其他线程的协作(如调用notify())或超时机制。
在Java中的体现线程处于BLOCKED状态(如竞争锁失败)。线程处于WAITINGTIMED_WAITING状态(如调用wait())。

3. 底层机制

(1) 阻塞状态(Blocked)

  • 资源竞争:线程因无法获取共享资源(如锁、I/O设备)被挂起。
  • 内核介入:由操作系统调度器管理,资源可用时自动唤醒线程。
  • 示例
    // 线程A持有锁,线程B尝试获取锁时被阻塞(BLOCKED)
    synchronized (lock) {
        // 临界区代码
    }

(2) 等待状态(Waiting)

  • 条件等待:线程主动进入等待,直到特定条件满足(如数据就绪、任务完成)。
  • 协作唤醒:需其他线程显式唤醒(如notify())或等待超时。
  • 示例
    // 线程调用wait()进入等待状态(WAITING)
    synchronized (lock) {
        while (!condition) {
            lock.wait(); // 释放锁并等待
        }
        // 条件满足后继续执行
    }

4. Java线程状态模型

Java明确区分了阻塞和等待状态(通过Thread.State枚举):

  • BLOCKED:线程因竞争锁失败而阻塞(如进入synchronized块时锁被占用)。
  • WAITING:线程因调用无超时的wait()join()等方法进入无限期等待。
  • TIMED_WAITING:线程因调用带超时的sleep()wait(timeout)等方法进入限期等待。

5. 状态转换流程

        +------------------------+    获取锁失败          +---------------------+
        |       RUNNABLE         | -------------------> |      BLOCKED        |
        +------------------------+                      +---------------------+
               |      ^                                         |
               |      | 锁可用                                  |
               |      +-----------------------------------------+
               |
               | 调用wait()/join()/park()
               v
        +------------------------+    超时或唤醒          +---------------------+
        |      WAITING          | -------------------> |      RUNNABLE        |
        | 或 TIMED_WAITING      | <-------------------  +---------------------+
        +------------------------+    调用sleep(timeout)

6. 关键问题与示例

(1) 阻塞状态的问题

  • 死锁风险:多个线程互相持有对方所需资源,导致永久阻塞。
    // 线程A持有锁1,请求锁2;线程B持有锁2,请求锁1
    synchronized (lock1) {
        synchronized (lock2) { ... } // 死锁
    }

(2) 等待状态的问题

  • 无限等待:若未正确唤醒,线程可能永远挂起。
    synchronized (lock) {
        lock.wait(); // 若没有其他线程调用lock.notify(),将永久等待
    }

总结

特性阻塞状态(Blocked)等待状态(Waiting)
触发原因被动等待外部资源(如锁、I/O)。主动等待事件触发(如条件变量、超时)。
恢复方式资源可用时由系统自动唤醒。需其他线程显式唤醒或超时。
协作性无依赖其他线程的协作。依赖其他线程的协作(如notify())。
设计意义处理资源竞争问题(同步互斥)。实现线程间协调(生产者-消费者、任务依赖)。

实践建议

  1. 避免长时间阻塞:使用非阻塞I/O或超时机制(如tryLock())。
  2. 谨慎使用等待:确保唤醒逻辑完备,避免无限等待。
  3. 优先使用高层工具:如java.util.concurrent包中的ConditionCountDownLatch等,简化线程协作。