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

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

.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;
	}

	@Override
	public void run() {
		house.createProduct();
	}
}
消费者:

public class Customer extends Thread {
	private WareHouse house;

	public Customer(WareHouse house) {
		this.house = house;
	}

	@Override
	public 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_]等待,当前仓库大小为:0
Consume_Thread[_11_]等待,当前仓库大小为:0
Create_Thread[_10_]:生产了Product[10,Product__10],当前仓库大小为:1
Create_Thread[_8_]:生产了Product[8,Product__8],当前仓库大小为:2
Consume_Thread[_17_]:消费了[8,Product__8],当前仓库大小为:1
Consume_Thread[_11_]:消费了[10,Product__10],当前仓库大小为:0
Consume_Thread[_19_]等待,当前仓库大小为:0
Consume_Thread[_9_]等待,当前仓库大小为:0
Create_Thread[_14_]:生产了Product[14,Product__14],当前仓库大小为:1
Consume_Thread[_15_]:消费了[14,Product__14],当前仓库大小为:0
Create_Thread[_12_]:生产了Product[12,Product__12],当前仓库大小为:1
Consume_Thread[_13_]:消费了[12,Product__12],当前仓库大小为:0
Create_Thread[_20_]:生产了Product[20,Product__20],当前仓库大小为:1
Consume_Thread[_23_]:消费了[20,Product__20],当前仓库大小为:0
Consume_Thread[_9_]等待,当前仓库大小为:0
Consume_Thread[_19_]等待,当前仓库大小为:0
Create_Thread[_18_]:生产了Product[18,Product__18],当前仓库大小为:1
Consume_Thread[_21_]:消费了[18,Product__18],当前仓库大小为:0
Create_Thread[_16_]:生产了Product[16,Product__16],当前仓库大小为:1
Create_Thread[_24_]:生产了Product[24,Product__24],当前仓库大小为:2
Consume_Thread[_19_]:消费了[24,Product__24],当前仓库大小为:1
Create_Thread[_26_]:生产了Product[26,Product__26],当前仓库大小为:2
Consume_Thread[_9_]:消费了[26,Product__26],当前仓库大小为:1
Consume_Thread[_25_]:消费了[16,Product__16],当前仓库大小为:0
Create_Thread[_22_]:生产了Product[22,Product__22],当前仓库大小为:1
Consume_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:1
Thread_create>:[12],queue_size:2
Thread_create>:[16],queue_size:3
Thread_consume:[9],Product:[8]
Thread_consume:[11],Product:[12]
Thread_consume:[13],Product:[16]
Thread_create>:[10],queue_size:1
Thread_consume:[15],Product:[10]
Thread_consume:[19],Product:[14]
Thread_create>:[18],queue_size:1
Thread_create>:[20],queue_size:2
Thread_consume:[25],Product:[18]
Thread_create>:[24],queue_size:2
Thread_create>:[14],queue_size:2
Thread_consume:[17],Product:[20]
Thread_consume:[21],Product:[24]
Thread_consume:[23],Product:[22]
Thread_create>:[22],queue_size:0
Thread_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
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值