生产者消费者模式
此博文部分借鉴于:
https://blog.csdn.net/u011109589/article/details/80519863
https://blog.csdn.net/wuyangyang555/article/details/80832816
通过一个容器来解决生产者和消费者的强耦合关系,生产者生成数据无需等待消费者索取,消费者无需直接索要数据。两者并不进行任何通讯,而是通过容器来进行操作。
优点
- 解耦
假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。 - 支持并发
使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。 - 支持闲忙不均
缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
实现生产者消费者模式
wait()和notify()方法
执行wait()方法,线程会进入waiting状态(阻塞状态),释放掉锁的monitor对象。notify()方法则可以唤醒阻塞状态的线程。
所以可以自定义一个阻塞队列(这里用链表代替),但队列满了的时候,通过wait(),使生产者线程阻塞,并释放队列对象的锁,以及唤醒消费者线程。当队列被清空时,消费者阻塞,唤醒生产者。
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage(10);
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
new Thread(producer).start();
new Thread(consumer).start();
}
static class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
static class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
static class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage(int maxSize) {
this.maxSize = maxSize;
this.storage = new LinkedList<>();
}
public synchronized void put() {
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("仓库里有了" + storage.size() + "个产品");
notify();
}
public synchronized void take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("拿到了"+storage.poll()+"现在仓库还剩下"+storage.size());
notify();
}
}
}
执行结果:
敲黑板!!!
从执行结果我们可以看出,在队列中有数据的情况下,消费者生产者线程都处于运行态,由于消费速度、需求以及生产需求的不一致,就可能出现队列满、空的状态。这是wait()、notify()作用就出来。可以看出,生产者消费者模式很好地解决了,生产消费不一致,以及效率的问题。
使用Lock和Condition的await() / signal()方法
在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。Condition接口的await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和Object的wait()/ nofity()相同,完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全。下面来看代码:
public class ProducerConsumerModelCondition {
public static final Lock LOCK = new ReentrantLock();
public static final Condition fullCondition = LOCK.newCondition();
public static final Condition emptyCondition = LOCK.newCondition();
private static LinkedList<Integer> storage = new LinkedList<>();
public static void main(String[] args) {
new Producer(storage, 10).start();
new Consumer(storage).start();
}
static class Producer extends Thread {
private LinkedList<Integer> storage;
private int maxSize;
public Producer(LinkedList<Integer> storage, int maxSize) {
this.storage = storage;
this.maxSize = maxSize;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
LOCK.lock();
put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
LOCK.unlock();
}
}
}
public void put(int num) throws InterruptedException {
while (storage.size() == maxSize) {
System.out.println("仓库满了,生产者阻塞");
fullCondition.await();
}
storage.add(num);
System.out.println("仓库增加了:" + num + " 仓库里有了" + storage.size() + "个产品");
emptyCondition.signal();
}
}
static class Consumer extends Thread {
private LinkedList<Integer> storage;
public Consumer(LinkedList<Integer> storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
LOCK.lock();
take();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
LOCK.unlock();
}
}
}
public void take() throws InterruptedException {
while (storage.isEmpty()) {
System.out.println("仓库里没有东西了,消费者阻塞");
emptyCondition.await();
}
System.out.println("消费者消费了:" + storage.poll() + "仓库里还有" + storage.size() + "个产品");
fullCondition.signal();
}
}
}
使用阻塞队列(BlockingQueue)
我们这里使用ArrayBlockingQueue,它是一个已经在内部实现了同步的队列,实现方式采用的是我们第2种await()/ signal()方法。它可以在生成对象时指定容量大小。它用于阻塞操作的是put()和take()方法。
- put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。
- take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞。
实现如下:
public class ProducerConsumerModelBlockingQueue {
public static final ArrayBlockingQueue<Integer> storage = new ArrayBlockingQueue<>(10);
public static void main(String[] args) throws InterruptedException {
new Thread(new Producer(storage)).start();
Thread.sleep(1000);
new Thread(new Consumer(storage)).start();
}
static class Producer implements Runnable {
private ArrayBlockingQueue<Integer> storage;
public Producer(ArrayBlockingQueue<Integer> storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
put(i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void put(int n) throws InterruptedException {
storage.put(n);
System.out.println("仓库新增了:" + n);
}
}
static class Consumer implements Runnable {
private ArrayBlockingQueue<Integer> storage;
public Consumer(ArrayBlockingQueue<Integer> storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
try {
take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void take() throws InterruptedException {
Integer num = storage.take();
System.out.println("消费了:" + num + ";仓库还剩余:" + storage.size());
}
}
}
结果:
后语
以上就是3种生产者消费者的实现方法。当然,还有其他实现方式,不一一赘述。总结以上,前两种实现方法,实际上就是用代码自定义一个阻塞队列,来实现第三种方法中take()、put()的效果。