引入
我们的很多技术设计思路都来源于生活,生产/消费者模式也不例外。在现实世界中,我们把生产商品的一方称为生产者,把消费商品的一方称为消费者,有时生产者的生产速度特别快,但消费者的消费速度跟不上,就是所谓的“产能过剩”,相反,有时消费者的消费速度大于生产者的生产速度,也就是所谓的“供不应求”。
什么是生产/消费者模式?
生产/消费者模式是一种经典的程序设计模式。
其设计思路正如其名,通过两类角色,即生产者和消费者来实现。生产者负责生成数据或任务,并将其放入一个共享的缓冲区中;消费者则从缓冲区中获取数据或任务并进行处理。生产者和消费者之间通过缓冲区进行解耦,它们不需要直接相互通信或了解对方的具体工作细节,只需要关注缓冲区的状态和操作。
其核心优势:
- 解耦:生产者和消费者之间相互独立,它们只需要关注自己的生产和消费行为,而不需要了解对方的具体实现细节。这样可以降低系统的耦合度,提高系统的可维护性和可扩展性。
- 提高性能:生产者和消费者可以并行工作,生产者可以在消费者处理数据的同时继续生产数据,从而提高系统的整体性能。
- 缓冲作用:缓冲区可以起到缓冲数据的作用,当生产者生产数据的速度大于消费者消费数据的速度时,缓冲区可以暂存数据,避免数据丢失;当消费者消费数据的速度大于生产者生产数据的速度时,缓冲区可以提供数据,保证消费者不会因为没有数据而等待。
在java并发编程时,要实现生产/消费者模式,通常需要在两者之间增加一个阻塞队列作为媒介,有了媒介之后就相当于有了一个缓冲,平衡了两者的能力,整体的设计如上图所示,最上面是阻塞队列,左侧是生产者线程,生产者在生产数据后将数据存放在阻塞队列中,右侧是消费者线程,消费者获取阻塞队列中的数据。而中间分别代表生产者消费者之间互相通信的过程,因为无论阻塞队列是满还是空都可能会产生阻塞,阻塞之后就需要在合适的时机去唤醒被阻塞的线程。
那么什么时候阻塞线程需要被唤醒呢?
主要有两种情况:
- 当消费者看到阻塞队列为空时,开始进入等待,这时生产者一旦往队列中放入数据,就会通知所有的消费者,唤醒阻塞的消费者线程。
- 如果生产者发现队列已经满了,也会被阻塞,而一旦消费者获取数据之后就相当于队列空了一个位置,这时消费者就会通知所有正在阻塞的生产者进行生产。
相关重要方法的使用注意事项
我们通过下面几个问题去梳理 wait/notify/notifyAll 方法的使用注意事项。
为什么 wait 方法必须在 synchronized 保护的同步代码中使用?
wait 方法的源码注释如下:
/**
* Causes the current thread to wait until another thread invokes the
* {@link java.lang.Object#notify()} method or the
* {@link java.lang.Object#notifyAll()} method for this object.
* In other words, this method behaves exactly as if it simply
* performs the call {@code wait(0)}.
* <p>
* The current thread must own this object's monitor. The thread
* releases ownership of this monitor and waits until another thread
* notifies threads waiting on this object's monitor to wake up
* either through a call to the {@code notify} method or the
* {@code notifyAll} method. The thread then waits until it can
* re-obtain ownership of the monitor and resumes execution.
* <p>
* As in the one argument version, interrupts and spurious wakeups are
* possible, and this method should always be used in a loop:
* <pre>
*