Java 杂谈IT@程序员猿媛面试题

Java理解生产者-消费者设计模式

2019-04-10  本文已影响2人  殷天文

在实际的软件开发过程中,经常会碰到如下场景:某个模块负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类、函数、线程、进程等)。产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者;

生产者和消费者之间通过缓冲区(通常是一个阻塞队列)实现通讯, 生产者将产生的数据放入缓冲区,消费者从缓冲区中获取数据。

图片来源网络

举个栗子

去食堂吃饭,食堂大叔会先将饭做好,放到食堂窗口,下课了同学会去食堂打饭。

生产者(食堂大叔) -> 生产数据(做饭) -> 缓冲区(食堂窗口) -> 消费数据(打饭) -> 消费者(同学)

生产者消费者实现思路

图片来源网络
public class ProducerConsumerTest {
    public static void main(String[] args) {
        Buffer buffer = new Buffer();
        Producer p1 = new Producer(buffer, 1);
        Consumer c1 = new Consumer(buffer, 1);
        p1.start();
        c1.start();
    }
}

/**
 * 缓冲区
 */
class Buffer {
    private List<Integer> data = new ArrayList<>();
    private static final int MAX = 10;
    private static final int MIN = 0;

    public synchronized int get() {
        while (MIN == data.size()) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        Integer i = data.remove(0);
        notifyAll();
        return i;
    }

    public synchronized void put(int value) {
        while (MAX == data.size()) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
        data.add(value);
        notifyAll();
    }
}

class Consumer extends Thread {
    private Buffer buffer;
    private int number;

    public Consumer(Buffer b, int number) {
        buffer = b;
        this.number = number;
    }

    public void run() {
        int value;
        for (int i = 0; i < 10; i++) {
            // 从缓冲区中获取数据
            value = buffer.get();
            try {
                // 模拟消费数据
                sleep(1000);
            } catch (InterruptedException e) {
            }
            System.out.println("消费者 #" + this.number + " got: " + value);
        }
    }
}

class Producer extends Thread {
    private Buffer buffer;
    private int number;

    public Producer(Buffer b, int number) {
        buffer = b;
        this.number = number;
    }

    public void run() {
        for (int i = 0; i < 10; i++) {
            try {
                // 模拟生产数据
                sleep(500);
            } catch (InterruptedException e) {
            }
            
            // 将数据放入缓冲区
            buffer.put(i);
            System.out.println("生产者 #" + this.number + " put: " + i);
        }
    }
}

  1. wait()、notify/notifyAll() 必须在synchronized代码块执行,可保证当前只有一个线程获取到Buffer对象的锁。所以可以使用线程不安全的ArrayList充当了阻塞队列

  2. 当消费者获得锁,队列里有数据,消费并尝试唤醒生产者。如果队列里为空,调用 wait(),会挂起当前线程并释放锁,等待被生产者唤醒

  3. 当生产者获得锁,队列里数据没满,生产并尝试唤醒消费者。队列数据满了,调用wait(),挂起当前线程并释放锁,等待被消费者唤醒

上述使用最基本的Java代码实现生产者消费者模式,实际开发中我们可能会使用BlockingQueueReentrantLock等等这些更成熟的轮子,但是一通百通吧。感兴趣的可以戳这里 Java实现生产者和消费者的5种方式

为什么要使用生产者消费者模式

顺序执行不就可以了吗?生产者消费者到底有什么意义?

生产者直接调用消费者,两者是同步的(阻塞),如果消费者吞吐数据很慢,这时候生产者白白浪费大好时光。而使用这种模式之后,生产者将数据丢到缓冲区,继续生产,完全不依赖消费者,程序执行效率会大大提高。

生产者和消费者之间不直接依赖,通过缓冲区通讯,将两个类之间的耦合度降到最低。

参考

https://blog.csdn.net/u011109589/article/details/80519863

上一篇下一篇

猜你喜欢

热点阅读