生产者消费者模式并不是GOF提出的23种设计模式之一,23种设计模式都是建立在面向对象的基础之上的,但其实面向过程的编程中也有很多高效的编程模式,生产者消费者模式便是其中之一,它是我们编程过程中最常用的一种设计模式。
在java应用中,生产者/消费者模式的实现有以下五种:
1:wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
2.await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同。
3:BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,实现方式采用的是我们第2种await() / signal()方法。
4:PipedOutputStream和PipedInputStream是管道输出流和管道输入流,配合使用可以实现线程间通信。
5:Semaphore除了控制资源的多个副本的并发访问控制,也可以使用二进制信号量来实现类似synchronized关键字和Lock锁的并发访问控制功能。
源码地址:https://github.com/followwwind/javadesign
下面对上面三种进行实现:
/**
* @author wind
*/
public interface Storage {
/**
* 仓库最大存储量
*/
int MAX_SIZE = 100;
/**
* 生产num个产品
* @param num
*/
void produce(int num);
/**
* 消费num个产品
* @param num
*/
void consume(int num);
}
wait() / nofity()
/**
* wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。
* wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等等状态,让其他线程执行。
* notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。
* @author wind
*/
public class StorageThree implements Storage{
/**
* 仓库最大存储量
*/
private final int MAX_SIZE = 100;
/**
* 仓库存储的载体
*/
private LinkedList<Object> list = new LinkedList<>();
/**
* 生产num个产品
* @param num
*/
@Override
public void produce(int num) {
// 同步代码段
synchronized (list) {
// 如果仓库剩余容量不足
while (list.size() + num > MAX_SIZE) {
System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:"
+ list.size() + "/t暂时不能执行生产任务!");
try {
// 由于条件不满足,生产阻塞
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产条件满足情况下,生产num个产品
for (int i = 1; i <= num; ++i) {
list.add(new Object());
}
System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());
list.notifyAll();
}
}
/**
* 消费num个产品
* @param num
*/
@Override
public void consume(int num) {
// 同步代码段
synchronized (list) {
// 如果仓库存储量不足
while (list.size() < num) {
System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:"
+ list.size() + "/t暂时不能执行生产任务!");
try {
// 由于条件不满足,消费阻塞
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费条件满足情况下,消费num个产品
for (int i = 1; i <= num; ++i) {
list.remove();
}
System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());
list.notifyAll();
}
}
public LinkedList<Object> getList() {
return list;
}
public void setList(LinkedList<Object> list) {
this.list = list;
}
}
await()和signal()
/**
* 仓库类Storage实现缓冲区
* 在JDK5.0之后,Java提供了更加健壮的线程处理机制,包括同步、锁定、线程池等,它们可以实现更细粒度的线程控制。
* await()和signal()就是其中用来做同步的两种方法,它们的功能基本上和wait() / nofity()相同,
* 完全可以取代它们,但是它们和新引入的锁定机制Lock直接挂钩,具有更大的灵活性。
* 通过在Lock对象上调用newCondition()方法,将条件变量和一个锁对象进行绑定,进而控制并发程序访问竞争资源的安全
* @author wind
*/
public class StorageOne implements Storage {
/**
* 仓库存储的载体
*/
private LinkedList<Object> list = new LinkedList<>();
/**
* 锁
*/
private final Lock lock = new ReentrantLock();
/**
* 仓库满的条件变量
*/
private final Condition full = lock.newCondition();
/**
* 仓库空的条件变量
*/
private final Condition empty = lock.newCondition();
/**
* 生产num个产品
* @param num
*/
@Override
public void produce(int num) {
// 获得锁
lock.lock();
// 如果仓库剩余容量不足
while (list.size() + num > MAX_SIZE) {
System.out.println("【要生产的产品数量】:" + num + "/t【库存量】:" + list.size()
+ "/t暂时不能执行生产任务!");
try {
// 由于条件不满足,生产阻塞
full.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 生产条件满足情况下,生产num个产品
for (int i = 1; i <= num; ++i) {
list.add(new Object());
}
System.out.println("【已经生产产品数】:" + num + "/t【现仓储量为】:" + list.size());
// 唤醒其他所有线程
full.signalAll();
empty.signalAll();
// 释放锁
lock.unlock();
}
/**
* 消费num个产品
* @param num
*/
@Override
public void consume(int num) {
// 获得锁
lock.lock();
// 如果仓库存储量不足
while (list.size() < num) {
System.out.println("【要消费的产品数量】:" + num + "/t【库存量】:" + list.size()
+ "/t暂时不能执行生产任务!");
try {
// 由于条件不满足,消费阻塞
empty.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 消费条件满足情况下,消费num个产品
for (int i = 1; i <= num; ++i) {
list.remove();
}
System.out.println("【已经消费产品数】:" + num + "/t【现仓储量为】:" + list.size());
// 唤醒其他所有线程
full.signalAll();
empty.signalAll();
// 释放锁
lock.unlock();
}
public LinkedList<Object> getList() {
return list;
}
public void setList(LinkedList<Object> list) {
this.list = list;
}
}
3.
BlockingQueue
/**
* 仓库类Storage实现缓冲区
* BlockingQueue是JDK5.0的新增内容,它是一个已经在内部实现了同步的队列,实现方式采用的是我们第2种await() / signal()方法。
* 它可以在生成对象时指定容量大小。它用于阻塞操作的是put()和take()方法。
* put()方法:类似于我们上面的生产者线程,容量达到最大时,自动阻塞。
* take()方法:类似于我们上面的消费者线程,容量为0时,自动阻塞。
* @author wind
*/
public class StorageTwo implements Storage {
/**
* 仓库存储的载体
*/
private LinkedBlockingQueue<Object> list = new LinkedBlockingQueue<>(MAX_SIZE);
/**
* 生产num个产品
* @param num
*/
@Override
public void produce(int num) {
// 如果仓库剩余容量为0
if (list.size() == MAX_SIZE) {
System.out.println("【库存量】:" + MAX_SIZE + "/t暂时不能执行生产任务!");
}
// 生产条件满足情况下,生产num个产品
for (int i = 1; i <= num; ++i) {
try {
// 放入产品,自动阻塞
list.put(new Object());
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("【现仓储量为】:" + list.size());
}
}
/**
* 消费num个产品
* @param num
*/
@Override
public void consume(int num) {
// 如果仓库存储量不足
if (list.size() == 0) {
System.out.println("【库存量】:0/t暂时不能执行生产任务!");
}
// 消费条件满足情况下,消费num个产品
for (int i = 1; i <= num; ++i) {
try {
// 消费产品,自动阻塞
list.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("【现仓储量为】:" + list.size());
}
public LinkedBlockingQueue<Object> getList() {
return list;
}
public void setList(LinkedBlockingQueue<Object> list) {
this.list = list;
}
}
4.消费者类Consumer继承线程类Thread
public class Consumer extends Thread{
/**
* 每次消费的产品数量
*/
private int num;
/**
* 所在放置的仓库
*/
private Storage storage;
/**
* 构造函数,设置仓库
* @param storage
*/
public Consumer(Storage storage) {
this.storage = storage;
}
public Consumer(int num, Storage storage) {
this.num = num;
this.storage = storage;
}
/**
* 线程run函数
*/
@Override
public void run() {
consume(num);
}
/**
* 调用仓库Storage的生产函数
* @param num
*/
public void consume(int num) {
storage.consume(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Storage getStorage() {
return storage;
}
public void setStorage(Storage storage) {
this.storage = storage;
}
}
5.生产者类Producer继承线程类Thread
public class Producer extends Thread{
/**
* 每次生产的产品数量
*/
private int num;
/**
* 所在放置的仓库
*/
private Storage storage;
/**
* 构造函数,设置仓库
* @param storage
*/
public Producer(Storage storage) {
this.storage = storage;
}
public Producer(int num, Storage storage) {
this.num = num;
this.storage = storage;
}
/**
* 线程run函数
*/
@Override
public void run() {
produce(num);
}
/**
* 调用仓库Storage的生产函数
* @param num
*/
public void produce(int num) {
storage.produce(num);
}
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
public Storage getStorage() {
return storage;
}
public void setStorage(Storage storage) {
this.storage = storage;
}
}
6.测试
/**
* 测试类
* @author wind
*/
public class Test {
private static Storage getStorage(String name){
Storage storage = null;
switch (name){
//await/signal
case "one" : storage = new StorageOne(); break;
//blockingqueue
case "two" : storage = new StorageTwo(); break;
//wait/nofity
default: new StorageThree(); break;
}
return storage;
}
public static void main(String[] args) {
// 仓库对象
Storage storage = getStorage("one");
// 生产者对象和产品生产数量
Producer p1 = new Producer(10, storage);
Producer p2 = new Producer(10, storage);
Producer p3 = new Producer(10, storage);
Producer p4 = new Producer(10, storage);
Producer p5 = new Producer(10, storage);
Producer p6 = new Producer(10, storage);
Producer p7 = new Producer(80, storage);
// 消费者对象和产品消费数量
Consumer c1 = new Consumer(50, storage);
Consumer c2 = new Consumer(20, storage);
Consumer c3 = new Consumer(30, storage);
// 线程开始执行
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}