并发编程(10)生产者/消费者

来源:互联网 发布:淘宝一淘网首页 编辑:程序博客网 时间:2024/06/01 08:07

      生产者、消费者设计模式是多线程中经典的设计模式,也是面试常被问到的问题,本章我们将讨论多线程中生产者、消费者的问题。

.1)生产者、消费者问题描述

      生产者、消费者设计模式,其实是一种“生产-消费-仓库--产品”模型,它应该具备以下几种特征:

    (1)生产者只在仓库未满时生产产品,仓库满则停止生产;

    (2)消费者只在仓库有产品余量时消费,仓库空则等待;

    (3)当消费者发现仓库没有产品余量时通知生产者去生产;

    (4)当生产者生产了产品后通知等待的消费者去消费。

.2)解决方案

(a)使用wait/notify/synchronized方法实现:

首先我们创建一个产品类:

//产品类public class Product {//产品编号private int productId;//产品名称private String productName;public int getProductId() {return productId;}public void setProductId(int productId) {this.productId = productId;}public String getProductName() {return productName;}public void setProductName(String productName) {this.productName = productName;}public Product(int productId, String productName) {super();this.productId = productId;this.productName = productName;}}
这个类比较简单,主要创建一个产品类(产品编号、产品名称),我们再来创建一个仓库,进行产品的管理

//仓库类public class WareHouse {// 仓库容量private int size;LinkedList<Product> queue = new LinkedList<Product>();public WareHouse(int size) {this.size = size;}public void createProduct() {try {synchronized (queue) {while (queue.size() == size) {System.out.println("仓库已满");System.out.println("Create_Thread[_"+ Thread.currentThread().getId() + "_]" + "等待");queue.wait();}int i = (int) Thread.currentThread().getId();Product p = new Product(i, "Product__" + i);queue.addFirst(p);System.out.println("Create_Thread[_" + Thread.currentThread().getId()+ "_]:生产了Product[" + i + "," + p.getProductName() + "],当前仓库大小为:"+queue.size());queue.notifyAll();}} catch (InterruptedException e) {e.printStackTrace();}}public void consumeProduct() {try {synchronized (queue) {while (queue.size() == 0) {System.out.println("Consume_Thread[_"+ Thread.currentThread().getId() + "_]" + "等待,当前仓库大小为:"+queue.size());queue.wait();}System.out.println("Consume_Thread[_" + Thread.currentThread().getId()+ "_]:消费了[" + queue.getFirst().getProductId() + ","+ queue.getFirst().getProductName() + "],当前仓库大小为:"+(queue.size()-1));queue.removeFirst();queue.notifyAll();}} catch (InterruptedException e) {e.printStackTrace();}}public int getSize() {return size;}public void setSize(int size) {this.size = size;}public LinkedList<Product> getQueue() {return queue;}public void setQueue(LinkedList<Product> queue) {this.queue = queue;}}
仓库,拥有容量属性,并创建一个LinkedList进行product的保存,实现了两个方法,进行产品的创建与消费,再看看我们的生产者、消费者类,

public class Producer extends Thread {private WareHouse house;public Producer(WareHouse house) {this.house = house;}@Overridepublic void run() {house.createProduct();}}
消费者:

public class Customer extends Thread {private WareHouse house;public Customer(WareHouse house) {this.house = house;}@Overridepublic void run() {house.consumeProduct();}}
测试类:

public class Test {

    public static void main(String[] args) {
        WareHouse house = new WareHouse(5);
        for (int i = 0; i < 10; i++) {
            Thread pro1 = new Producer(house);
            Thread cus1 = new Customer(house);
            cus1.start();
            pro1.start();
        }
    }
}

来看看测试结果:

Consume_Thread[_9_]等待,当前仓库大小为:0Consume_Thread[_11_]等待,当前仓库大小为:0Create_Thread[_10_]:生产了Product[10,Product__10],当前仓库大小为:1Create_Thread[_8_]:生产了Product[8,Product__8],当前仓库大小为:2Consume_Thread[_17_]:消费了[8,Product__8],当前仓库大小为:1Consume_Thread[_11_]:消费了[10,Product__10],当前仓库大小为:0Consume_Thread[_19_]等待,当前仓库大小为:0Consume_Thread[_9_]等待,当前仓库大小为:0Create_Thread[_14_]:生产了Product[14,Product__14],当前仓库大小为:1Consume_Thread[_15_]:消费了[14,Product__14],当前仓库大小为:0Create_Thread[_12_]:生产了Product[12,Product__12],当前仓库大小为:1Consume_Thread[_13_]:消费了[12,Product__12],当前仓库大小为:0Create_Thread[_20_]:生产了Product[20,Product__20],当前仓库大小为:1Consume_Thread[_23_]:消费了[20,Product__20],当前仓库大小为:0Consume_Thread[_9_]等待,当前仓库大小为:0Consume_Thread[_19_]等待,当前仓库大小为:0Create_Thread[_18_]:生产了Product[18,Product__18],当前仓库大小为:1Consume_Thread[_21_]:消费了[18,Product__18],当前仓库大小为:0Create_Thread[_16_]:生产了Product[16,Product__16],当前仓库大小为:1Create_Thread[_24_]:生产了Product[24,Product__24],当前仓库大小为:2Consume_Thread[_19_]:消费了[24,Product__24],当前仓库大小为:1Create_Thread[_26_]:生产了Product[26,Product__26],当前仓库大小为:2Consume_Thread[_9_]:消费了[26,Product__26],当前仓库大小为:1Consume_Thread[_25_]:消费了[16,Product__16],当前仓库大小为:0Create_Thread[_22_]:生产了Product[22,Product__22],当前仓库大小为:1Consume_Thread[_27_]:消费了[22,Product__22],当前仓库大小为:0
如上,一个简单的生产者、消费者模型就搭建完毕了,这种方式通过synchronized、wait、notify等方法控制并发,属于线程的基本实现方法,其实java中已经有现成的实现类实现生产者、消费者模式,下面我们来看一下。

(b)BlockingQueue 阻塞队列方式

我们来修改一下仓库类:

public class WareHouse2 {// 仓库容量private int size;private LinkedBlockingQueue<Product> queue;public WareHouse2(int size) {this.size = size;this.queue=new LinkedBlockingQueue<Product>(size);}public void createProduct() {try {int i = (int) Thread.currentThread().getId();Product p = new Product(i, "Product__" + i);queue.put(p);System.out.println("Thread_create>:["+Thread.currentThread().getId()+"],queue_size:"+queue.size());} catch (InterruptedException e) {e.printStackTrace();}}public void consumeProduct() {try {Product pt=queue.take();System.out.println("Thread_consume:["+Thread.currentThread().getId()+"],Product:["+pt.getProductId()+"]");} catch (InterruptedException e) {e.printStackTrace();}}}
执行结果:

Thread_create>:[8],queue_size:1Thread_create>:[12],queue_size:2Thread_create>:[16],queue_size:3Thread_consume:[9],Product:[8]Thread_consume:[11],Product:[12]Thread_consume:[13],Product:[16]Thread_create>:[10],queue_size:1Thread_consume:[15],Product:[10]Thread_consume:[19],Product:[14]Thread_create>:[18],queue_size:1Thread_create>:[20],queue_size:2Thread_consume:[25],Product:[18]Thread_create>:[24],queue_size:2Thread_create>:[14],queue_size:2Thread_consume:[17],Product:[20]Thread_consume:[21],Product:[24]Thread_consume:[23],Product:[22]Thread_create>:[22],queue_size:0Thread_consume:[27],Product:[26]Thread_create>:[26],queue_size:0
通过源码,可以发现这种方式代码要简洁很多,而且在仓库类中,我们使用的put(),take()方法,当容量达到最大时,put会自动阻塞线程,take方法当容量为0时,也会自动阻塞线程,我们来看看源码

   public void put(E e) throws InterruptedException {        if (e == null) throw new NullPointerException();        // Note: convention in all put/take/etc is to preset local var        // holding count negative to indicate failure unless set.        int c = -1;        Node<E> node = new Node(e);        final ReentrantLock putLock = this.putLock;        final AtomicInteger count = this.count;        putLock.lockInterruptibly();        try {            /*             * Note that count is used in wait guard even though it is             * not protected by lock. This works because count can             * only decrease at this point (all other puts are shut             * out by lock), and we (or some other waiting put) are             * signalled if it ever changes from capacity. Similarly             * for all other uses of count in other wait guards.             */            while (count.get() == capacity) {                notFull.await();            }            enqueue(node);            c = count.getAndIncrement();            if (c + 1 < capacity)                notFull.signal();        } finally {            putLock.unlock();        }        if (c == 0)            signalNotEmpty();    }
 public E take() throws InterruptedException {        E x;        int c = -1;        final AtomicInteger count = this.count;        final ReentrantLock takeLock = this.takeLock;        takeLock.lockInterruptibly();        try {            while (count.get() == 0) {                notEmpty.await();            }            x = dequeue();            c = count.getAndDecrement();            if (c > 1)                notEmpty.signal();        } finally {            takeLock.unlock();        }        if (c == capacity)            signalNotFull();        return x;    }
我们可以发现,在方法中自动封装了wait(),signal()等同步方法,所以就不需要我们程序自己去处理,从难易程度及安全性准确性来讲,建议大家使用阻塞队列实现生产者、消费者模式。


0 0
原创粉丝点击