生产者消费者模型Java实现
生产者消费者模型
生产者消费者模型可以描述为:
①生产者持续生产,直到仓库放满产品,则停止生产进入等待状态;仓库不满后继续生产;
②消费者持续消费,直到仓库空,则停止消费进入等待状态;仓库不空后,继续消费;
③生产者可以有多个,消费者也可以有多个;

对应到程序中,仓库对应缓冲区,可以使用队列来作为缓冲区,并且这个队列应该是有界的,即最大容量是固定的;进入等待状态,则表示要阻塞当前线程,直到某一条件满足,再进行唤醒。
常见的实现方式主要有以下几种。
①使用wait()和notify()
②使用Lock和Condition
③使用信号量Semaphore
④使用JDK自带的阻塞队列
⑤使用管道流
使用wait()和notify()实现
前提是要熟悉Object的几个方法:
wait():当前线程释放锁,直到等到通知,再去获取锁sleep():当前线程休眠,但不释放锁notify():唤醒其他正在wait的线程
参考代码如下:
public class ProducerConsumer1 {
class Producer extends Thread {
private String threadName;
private Queue<Goods> queue;
private int maxSize;
public Producer(String threadName, Queue<Goods> queue, int maxSize) {
this.threadName = threadName;
this.queue = queue;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true) {
//模拟生产过程中的耗时操作
Goods goods = new Goods();
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (queue) {
while (queue.size() == maxSize) {
try {
System.out.println("队列已满,【" + threadName + "】进入等待状态");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(goods);
System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
queue.notifyAll();
}
}
}
}
class Consumer extends Thread {
private String threadName;
private Queue<Goods> queue;
public Consumer(String threadName, Queue<Goods> queue) {
this.threadName = threadName;
this.queue = queue;
}
@Override
public void run() {
while (true) {
Goods goods;
synchronized (queue) {
while (queue.isEmpty()) {
try {
System.out.println("队列已空,【" + threadName + "】进入等待状态");
queue.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
goods = queue.remove();
System.out.println("【" + threadName + "】消费了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
queue.notifyAll();
}
//模拟消费过程中的耗时操作
try {
Thread.sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test() {
int maxSize = 5;
Queue<Goods> queue = new LinkedList<>();
Thread producer1 = new Producer("生产者1", queue, maxSize);
Thread producer2 = new Producer("生产者2", queue, maxSize);
Thread producer3 = new Producer("生产者3", queue, maxSize);
Thread consumer1 = new Consumer("消费者1", queue);
Thread consumer2 = new Consumer("消费者2", queue);
producer1.start();
producer2.start();
producer3.start();
consumer1.start();
consumer2.start();
while (true) {
}
}
}
几个注意的地方:
①确定锁的对象是队列
queue;②不要把生产过程和消费过程写在同步块中,这些操作无需同步,同步的仅仅是放入和取出这两个动作;
③因为是持续生产,持续消费,要用
while(true){...}的方式将【生产、放入】或【取出、消费】的操作都一直进行。④但由于是对队列使用
synchronized的方式加锁,同一时刻,要么在放入,要么在取出,两者不能同时进行。
使用Lock和Condition实现
前提是要熟悉Lock接口以及常用实现类ReentrantLock,以及Condition的两个常用方法:
await():等待Condition的满足,会释放锁signal():唤醒其他正在等待该Condition的线程
参考代码如下:
public class ProducerConsumer2 {
class Producer extends Thread {
private String threadName;
private Queue<Goods> queue;
private Lock lock;
private Condition notFullCondition;
private Condition notEmptyCondition;
private int maxSize;
public Producer(String threadName, Queue<Goods> queue, Lock lock, Condition notFullCondition, Condition notEmptyCondition, int maxSize) {
this.threadName = threadName;
this.queue = queue;
this.lock = lock;
this.notFullCondition = notFullCondition;
this.notEmptyCondition = notEmptyCondition;
this.maxSize = maxSize;
}
@Override
public void run() {
while (true) {
//模拟生产过程中的耗时操作
Goods goods = new Goods();
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.lock();
try {
while (queue.size() == maxSize) {
try {
System.out.println("队列已满,【" + threadName + "】进入等待状态");
notFullCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
queue.add(goods);
System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
notEmptyCondition.signalAll();
} finally {
lock.unlock();
}
}
}
}
class Consumer extends Thread {
private String threadName;
private Queue<Goods> queue;
private Lock lock;
private Condition notFullCondition;
private Condition notEmptyCondition;
public Consumer(String threadName, Queue<Goods> queue, Lock lock, Condition notFullCondition, Condition notEmptyCondition) {
this.threadName = threadName;
this.queue = queue;
this.lock = lock;
this.notFullCondition = notFullCondition;
this.notEmptyCondition = notEmptyCondition;
}
@Override
public void run() {
while (true) {
Goods goods;
lock.lock();
try {
while (queue.isEmpty()) {
try {
System.out.println("队列已空,【" + threadName + "】进入等待状态");
notEmptyCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
goods = queue.remove();
System.out.println("【" + threadName + "】消费了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
notFullCondition.signalAll();
} finally {
lock.unlock();
}
//模拟消费过程中的耗时操作
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test() {
int maxSize = 5;
Queue<Goods> queue = new LinkedList<>();
Lock lock = new ReentrantLock();
Condition notEmptyCondition = lock.newCondition();
Condition notFullCondition = lock.newCondition();
Thread producer1 = new ProducerConsumer2.Producer("生产者1", queue, lock, notFullCondition, notEmptyCondition, maxSize);
Thread producer2 = new ProducerConsumer2.Producer("生产者2", queue, lock, notFullCondition, notEmptyCondition, maxSize);
Thread producer3 = new ProducerConsumer2.Producer("生产者3", queue, lock, notFullCondition, notEmptyCondition, maxSize);
Thread consumer1 = new ProducerConsumer2.Consumer("消费者1", queue, lock, notFullCondition, notEmptyCondition);
Thread consumer2 = new ProducerConsumer2.Consumer("消费者2", queue, lock, notFullCondition, notEmptyCondition);
Thread consumer3 = new ProducerConsumer2.Consumer("消费者3", queue, lock, notFullCondition, notEmptyCondition);
producer1.start();
producer2.start();
producer3.start();
consumer1.start();
consumer2.start();
consumer3.start();
while (true) {
}
}
}
要注意的地方:
放入和取出操作均是用的同一个锁,所以在同一时刻,要么在放入,要么在取出,两者不能同时进行。因此,与使用wait()和notify()实现类似,这种方式的实现并不能最大限度地利用缓冲区(即例子中的队列)。如果要实现同一时刻,既可以放入又可以取出,则要使用两个重入锁,分别控制放入和取出的操作,具体实现可以参考
LinkedBlockingQueue。
使用信号量Semaphore实现
前提是熟悉信号量Semaphore的使用方式,尤其是release()方法,Semaphore在release之前不必一定要先acquire。(如果不熟悉Semaphore,可以参考阅读【多线程与并发】Java并发工具类)
There is no requirement that a thread that releases a permit must
have acquired that permit by calling acquire.
Correct usage of a semaphore is established by programming convention
in the application.
参考代码如下:
public class ProducerConsumer4 {
class Producer extends Thread {
private String threadName;
private Queue<Goods> queue;
private Semaphore queueSizeSemaphore;
private Semaphore concurrentWriteSemaphore;
private Semaphore notEmptySemaphore;
public Producer(String threadName, Queue<Goods> queue, Semaphore concurrentWriteSemaphore, Semaphore queueSizeSemaphore, Semaphore notEmptySemaphore) {
this.threadName = threadName;
this.queue = queue;
this.concurrentWriteSemaphore = concurrentWriteSemaphore;
this.queueSizeSemaphore = queueSizeSemaphore;
this.notEmptySemaphore = notEmptySemaphore;
}
@Override
public void run() {
while (true) {
//模拟生产过程中的耗时操作
Goods goods = new Goods();
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
queueSizeSemaphore.acquire();//获取队列未满的信号量
concurrentWriteSemaphore.acquire();//获取读写的信号量
queue.add(goods);
System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
concurrentWriteSemaphore.release();
notEmptySemaphore.release();
}
}
}
}
class Consumer extends Thread {
private String threadName;
private Queue<Goods> queue;
private Semaphore queueSizeSemaphore;
private Semaphore concurrentWriteSemaphore;
private Semaphore notEmptySemaphore;
public Consumer(String threadName, Queue<Goods> queue, Semaphore concurrentWriteSemaphore, Semaphore queueSizeSemaphore, Semaphore notEmptySemaphore) {
this.threadName = threadName;
this.queue = queue;
this.concurrentWriteSemaphore = concurrentWriteSemaphore;
this.queueSizeSemaphore = queueSizeSemaphore;
this.notEmptySemaphore = notEmptySemaphore;
}
@Override
public void run() {
while (true) {
Goods goods;
try {
notEmptySemaphore.acquire();
concurrentWriteSemaphore.acquire();
goods = queue.remove();
System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
concurrentWriteSemaphore.release();
queueSizeSemaphore.release();
}
//模拟消费过程中的耗时操作
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test() {
int maxSize = 5;
Queue<Goods> queue = new LinkedList<>();
Semaphore concurrentWriteSemaphore = new Semaphore(1);
Semaphore notEmptySemaphore = new Semaphore(0);
Semaphore queueSizeSemaphore = new Semaphore(maxSize);
Thread producer1 = new ProducerConsumer4.Producer("生产者1", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore);
Thread producer2 = new ProducerConsumer4.Producer("生产者2", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore);
Thread producer3 = new ProducerConsumer4.Producer("生产者3", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore);
Thread consumer1 = new ProducerConsumer4.Consumer("消费者1", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore);
Thread consumer2 = new ProducerConsumer4.Consumer("消费者2", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore);
Thread consumer3 = new ProducerConsumer4.Consumer("消费者3", queue, concurrentWriteSemaphore, queueSizeSemaphore, notEmptySemaphore);
producer1.start();
producer2.start();
producer3.start();
consumer1.start();
consumer2.start();
consumer3.start();
while (true) {
}
}
}
要注意的地方:
①理解代码中的三个信号量的含义
queueSizeSemaphore:(其中的许可证数量,可以理解为队列中可以再放入多少个元素),该信号量的许可证初始数量为仓库大小,即maxSize;生产者每放置一个商品,则该信号量-1,即执行acquire(),表示队列中已经添加了一个元素,要减少一个许可证;消费者每取出一个商品,该信号量+1,即执行release(),表示队列中已经少了一个元素,再给你一个许可证。
notEmptySemaphore:(其中的许可证数量,可以理解为队列中可以取出多少个元素),该信号量的许可证初始数量为0;生产者每放置一个商品,则该信号量+1,即执行release(),表示队列中添加了一个元素;消费者每取出一个商品,该信号量-1,即执行acquire(),表示队列中已经少了一个元素,要减少一个许可证;
concurrentWriteSemaphore,相当于一个写锁,在放入或取出商品的时候,都需要先获取再释放许可证。②由于实现中,使用了
concurrentWriteSemaphore实现了对队列并发写的控制,在同一时刻,只能对队列进行一种操作:放入或取出。假如把concurrentWriteSemaphore中的信号量初始化为2或者2以上的值,就会出现多个生产者同时放入或多个消费者同时消费的情况,而使用的LinkedList是不允许并发进行这种修改的,否则会出现溢出或取空的情况。所以,concurrentWriteSemaphore只能设置为1,也就导致性能与使用wait() / notify()方式类似,性能不高。
使用jdk自带的阻塞队列实现
前提是要记住两个阻塞取放方法,因为阻塞队列提供了很多存取元素的方法,几种存取方式在队列已满/已空时采取的措施如下:
| 方法/方式处理 | 抛出异常 | 返回特殊值 | 一直阻塞 | 超时退出 |
|---|---|---|---|---|
| 插入 | add(e) | offer(e) | put(e) | offer(e, time, unit) |
| 移除 | remove() | poll() | take() | poll(time, unit) |
| 检查 | element() | peek() | 不可用 | 不可用 |
所以,在这里,要选用put()和take()这两个会阻塞的方法。
参考代码如下:
public class ProducerConsumer3 {
class Producer extends Thread {
private String threadName;
private BlockingQueue<Goods> queue;
public Producer(String threadName, BlockingQueue<Goods> queue) {
this.threadName = threadName;
this.queue = queue;
}
@Override
public void run() {
while (true){
Goods goods = new Goods();
try {
//模拟生产过程中的耗时操作
Thread.sleep(new Random().nextInt(100));
queue.put(goods);
System.out.println("【" + threadName + "】生产了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer extends Thread {
private String threadName;
private BlockingQueue<Goods> queue;
public Consumer(String threadName, BlockingQueue<Goods> queue) {
this.threadName = threadName;
this.queue = queue;
}
@Override
public void run() {
while (true){
try {
Goods goods = queue.take();
System.out.println("【" + threadName + "】消费了一个商品:【" + goods.toString() + "】,目前商品数量:" + queue.size());
//模拟消费过程中的耗时操作
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
@Test
public void test() {
int maxSize = 5;
BlockingQueue<Goods> queue = new LinkedBlockingQueue<>(maxSize);
Thread producer1 = new ProducerConsumer3.Producer("生产者1", queue);
Thread producer2 = new ProducerConsumer3.Producer("生产者2", queue);
Thread producer3 = new ProducerConsumer3.Producer("生产者3", queue);
Thread consumer1 = new ProducerConsumer3.Consumer("消费者1", queue);
Thread consumer2 = new ProducerConsumer3.Consumer("消费者2", queue);
producer1.start();
producer2.start();
producer3.start();
consumer1.start();
consumer2.start();
while (true) {
}
}
}
要注意的地方:
如果使用LinkedBlockingQueue作为队列实现,则可以实现:在同一时刻,既可以放入又可以取出,因为LinkedBlockingQueue内部使用了两个重入锁,分别控制取出和放入。
如果使用ArrayBlockingQueue作为队列实现,则在同一时刻只能放入或取出,因为ArrayBlockingQueue内部只使用了一个重入锁来控制并发修改操作。
使用管道流实现
//TODO
无锁的缓存框架: Disruptor
BlockingQueue 实现生产者和消费者模式简单易懂,但是BlockingQueue并不是一个高性能的实现:它完全使用锁和阻塞来实现线程之间的同步。在高并发的场合,它的性能并不是特别的优越。(ConconcurrentLinkedQueue是一个高性能的队列,但并不没有实现BlockingQueue接口,即不支持阻塞操作)。
Disruptor是LMAX公司开发的高效的无锁缓存队列。它使用无锁的方式实现了一个环形队列,非常适合于实现生产者和消费者模式,如:事件和消息的发布。
//TODO 应用场景的代码实现
参考
Java 实现生产者 – 消费者模型:各种实现方式的性能
高性能的生产者-消费者:无锁的实现:无锁实现
Java生产者和消费者模型的5种实现方式
生产者/消费者问题的多种Java实现方式
Java阻塞队列ArrayBlockingQueue和LinkedBlockingQueue实现原理分析:两种常用阻塞队列的区别
作者:maxwellyue
链接:https://www.jianshu.com/p/7cbb6b0bbabc
来源:简书
生产者消费者模型Java实现的更多相关文章
- 生产者消费者模型java
马士兵老师的生产者消费者模型,我感觉理解了生产者消费者模型,基本懂了一半多线程. public class ProducerConsumer { public static void main(Str ...
- 生产者消费者模型-Java代码实现
什么是生产者-消费者模式 比如有两个进程A和B,它们共享一个固定大小的缓冲区,A进程产生数据放入缓冲区,B进程从缓冲区中取出数据进行计算,那么这里其实就是一个生产者和消费者的模式,A相当于生产者,B相 ...
- 第23章 java线程通信——生产者/消费者模型案例
第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...
- Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)
生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题.有如下几个常见的实现方法: 1. wait()/notify() 2. lock & condition 3. Blockin ...
- Java多线程15:Queue、BlockingQueue以及利用BlockingQueue实现生产者/消费者模型
Queue是什么 队列,是一种数据结构.除了优先级队列和LIFO队列外,队列都是以FIFO(先进先出)的方式对各个元素进行排序的.无论使用哪种排序方式,队列的头都是调用remove()或poll()移 ...
- Java生产者消费者模型
在Java中线程同步的经典案例,不同线程对同一个对象同时进行多线程操作,为了保持线程安全,数据结果要是我们期望的结果. 生产者-消费者模型可以很好的解释这个现象:对于公共数据data,初始值为0,多个 ...
- 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
- Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会有解决很多问题]生产者消费者模型
http://blog.csdn.net/a352193394/article/details/39503857 Java多线程之~~~使用Exchanger在线程之间交换数据[这个结合多线程并行会 ...
- Java实现多线程生产者消费者模型及优化方案
生产者-消费者模型是进程间通信的重要内容之一.其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案. /* 单生产者.单消费者生产烤鸭 ...
随机推荐
- 【亲测可行,图片宽度高度自适应】c# Graphics MeasureString精确测量字体宽度
, , ) { int count = number.Length; //需要配置的字段 //Font f = new Font("Microsoft Sans Serif", f ...
- Jenkins配置Publish Over SSH讲解说明
原创 Jenkins配置Publish Over SSH讲解说明 2018-08-22 11:59:30 M.Blog 阅读数 3764更多 分类专栏: Jenkins 版权声明:本文为博主原创文 ...
- jenkins rpm卸载
rpm卸载 1.rpm -e jenkins rpm -ql jenkins 检查是否卸载成功 2.彻底删除残留文件:find / -iname jenkins | xargs -n 1000 rm ...
- Python——循环语句
while循环: 通常使用在当满足某一条件时进行的循环语句. 例如: while True: #当为True时进行循环,这个搭配就是死循环 print(1) while count < 10: ...
- SQL SERVER-修改实例的排序规则
系统库是无法直接修改的,只能修改用户数据库排序规则(要先解决依赖项.如表函数): ALTER DATABASE [xx] COLLATE Chinese_PRC_CI_AS 修改实例的排序规则,使用安 ...
- Android笔记(六十一)动态添加组件
想要一个功能,点击按钮,可以在已有的布局上,新添加一组组件. 动态的创建组件,本质上还是创建组件,只不过是在程序中根据逻辑来创建.大致步骤是找到要创建控件的位置,然后将要创建的组件添加进去. 看代码: ...
- Mysql5.7降级到5.6遇到的坑
任何版本的升级或降级都存在兼容的问题,DB更为明显,废话不多说,直接进入主题,问题描述如下: Specified key was too long; max key length is 767 byt ...
- 【CMDB】获取服务器数据
一.通过agent的方式 原理:服务器定制执行py文件通过subprocess模块采集数据发送给数据收集的机器 数据收集的机器:192.168.11.62 服务器:192.168.11.169 数据收 ...
- cpio命令
RPM包中文件提取 cpio命令主要有三种基本模式:"-o"模式指的是copy-out模式,就是把数据备份到文件库中:"-i"模式指的是copy-in模式,就是 ...
- new 运算符的原理
关于 new 运算符的原理: 1.红宝书上解释: (1)创建一个新对象 (2)将构造函数的作用域赋给新对象 (3)执行构造函数中的代码 (4)返回新对象 2.MDN上的解释: (1)一个继承自 Foo ...