在 Java 编程中,阻塞(Blocking) 和等待(Waiting) 是两个常见的概念,特别是在多线程编程中。它们虽然看起来相似,但实际上有着本质的区别。
一、阻塞(Blocking)
阻塞是指线程在执行过程中被暂停,直到满足某些条件才能继续执行。这个暂停通常是由操作系统调度器控制的,常见的阻塞情况包括:
-
I/O 阻塞:
- 当一个线程进行输入输出操作时(比如读写文件、网络通信等),如果数据没有准备好,线程会进入阻塞状态,直到数据准备就绪才能继续执行。
-
资源争用阻塞:
- 当一个线程需要获取一个资源(例如锁)而该资源被其他线程占用时,当前线程会被阻塞,直到资源被释放。
阻塞的线程会被操作系统从可运行队列中移除,直到条件满足再恢复执行。阻塞的线程不会消耗 CPU 资源。
阻塞的特点:
- 阻塞通常是外部条件决定的,例如 I/O 操作、等待锁等。
- 线程进入阻塞后,会被操作系统挂起,直到事件完成或外部条件满足。
- 阻塞状态下的线程会被挂起,无法继续执行任务,直到外部事件(如 I/O 完成、锁释放等)发生。
二、等待(Waiting)
等待通常指线程进入一个等待状态,直到其他线程通过某种方式通知它继续执行。在 Java 中,线程可以通过调用 Object.wait()
、Thread.join()
或 Lock
的 Condition.await()
等方法进入等待状态。通常情况下,线程会在这些方法中主动放弃 CPU 时间,等待某个条件满足时再继续执行。
-
Object.wait()
:- 当前线程会进入一个等待队列,并放弃持有的锁。线程进入等待状态,直到其他线程调用
notify()
或notifyAll()
来唤醒它。
- 当前线程会进入一个等待队列,并放弃持有的锁。线程进入等待状态,直到其他线程调用
-
Thread.join()
:- 如果一个线程调用
join()
方法,当前线程会等待被调用线程执行完毕,才能继续执行。
- 如果一个线程调用
-
Condition.await()
:- 在使用显式锁(如
ReentrantLock
)时,可以通过Condition
来实现等待和通知机制。
- 在使用显式锁(如
等待的特点:
- 等待是由线程自身主动触发的,线程进入等待状态时,可能会在等待队列中等待某个条件(如事件发生、其他线程的操作等)。
- 线程进入等待后,必须等待其他线程的通知才能继续执行。通常需要其他线程调用
notify()
、notifyAll()
或其他方式来唤醒。 - 等待的线程会放弃 CPU 时间,直到被通知或者条件满足。
三、阻塞与等待的对比
特性 | 阻塞(Blocking) | 等待(Waiting) |
---|---|---|
发生的原因 | 外部条件导致,如 I/O 操作、资源争用等 | 线程主动进入等待,直到被通知或条件满足 |
线程状态 | 线程会进入阻塞队列,无法继续执行 | 线程进入等待队列,必须等待其他线程通知 |
是否主动放弃 CPU | 线程会被挂起,外部条件满足后恢复执行 | 线程主动放弃 CPU,直到被唤醒或条件满足 |
典型方法 | Thread.sleep() 、I/O 操作、锁等待等 | Object.wait() 、Thread.join() 、Condition.await() |
需要通知 | 不需要其他线程通知 | 需要其他线程通知,如 notify() 或 notifyAll() |
四、总结
- 阻塞是指线程由于外部条件(如等待 I/O 操作、获取资源等)无法继续执行,线程会被挂起,直到外部条件满足。
- 等待是指线程主动进入等待状态,通常是因为它在等待其他线程的通知或某个条件的变化。
这两个概念的关键区别在于 谁控制线程的暂停:阻塞通常是由外部事件(如 I/O 操作)控制的,而等待是由线程自身决定并等待其他线程的唤醒。