三种方式实现生产者消费者模式

生产者消费者模式

此博文部分借鉴于:
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()的效果。

猜你喜欢

转载自blog.csdn.net/weixin_42812754/article/details/105617757