Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)
生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题。有如下几个常见的实现方法:
1. wait()/notify()
2. lock & condition
3. BlockingQueue
下面来逐一分析。
1. wait()/notify()
第一种实现,利用根类Object的两个方法wait()/notify(),来停止或者唤醒线程的执行;这也是最原始的实现。
public class WaitNotifyBroker<T> implements Broker<T> {
private final Object[] items;
private int takeIndex;
private int putIndex;
private int count;
public WaitNotifyBroker(int capacity) {
this.items = new Object[capacity];
}
@SuppressWarnings("unchecked")
@Override
public T take() {
T tmpObj = null;
try {
synchronized (items) {
while (0 == count) {
items.wait();
}
tmpObj = (T) items[takeIndex];
if (++takeIndex == items.length) {
takeIndex = 0;
}
count--;
items.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
return tmpObj;
}
@Override
public void put(T obj) {
try {
synchronized (items) {
while (items.length == count) {
items.wait();
}
items[putIndex] = obj;
if (++putIndex == items.length) {
putIndex = 0;
}
count++;
items.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里利用Array构造一个Buffer去存取数据,并利用count, putIndex和takeIndex来保证First-In-First-Out。
如果利用LinkedList来代替Array,相对来说会稍微简单些。
LinkedList的实现,可以参考《Java 7 Concurrency Cookbook》第2章wait/notify。
2. lock & condition
lock & condition,实际上也实现了类似synchronized和wait()/notify()的功能,但在加锁和解锁、暂停和唤醒方面,更加细腻和可控。
在JDK的BlockingQueue的默认实现里,也是利用了lock & condition。此文也详细介绍了怎么利用lock&condition写BlockingQueue,这里换LinkedList再实现一次:
public class LockConditionBroker<T> implements Broker<T> {
private final ReentrantLock lock;
private final Condition notFull;
private final Condition notEmpty;
private final int capacity;
private LinkedList<T> items;
public LockConditionBroker(int capacity) {
this.lock = new ReentrantLock();
this.notFull = lock.newCondition();
this.notEmpty = lock.newCondition();
this.capacity = capacity;
items = new LinkedList<T>();
}
@Override
public T take() {
T tmpObj = null;
lock.lock();
try {
while (items.size() == 0) {
notEmpty.await();
}
tmpObj = items.poll();
notFull.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
return tmpObj;
}
@Override
public void put(T obj) {
lock.lock();
try {
while (items.size() == capacity) {
notFull.await();
}
items.offer(obj);
notEmpty.signalAll();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
3. BlockingQueue
最后这种方法,也是最简单最值得推荐的。利用并发包提供的工具:阻塞队列,将阻塞的逻辑交给BlockingQueue。
实际上,上述1和2的方法实现的Broker类,也可以视为一种简单的阻塞队列,不过没有标准包那么完善。
public class BlockingQueueBroker<T> implements Broker<T> {
private final BlockingQueue<T> queue;
public BlockingQueueBroker() {
this.queue = new LinkedBlockingQueue<T>();
}
@Override
public T take() {
try {
return queue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
return null;
}
@Override
public void put(T obj) {
try {
queue.put(obj);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
我们的队列封装了标注包里的LinkedBlockingQueue,十分简单高效。
接下来,就是一个1P2C的例子:
public interface Broker<T> {
T take();
void put(T obj);
}
public class Producer implements Runnable {
private final Broker<Integer> broker;
private final String name;
public Producer(Broker<Integer> broker, String name) {
this.broker = broker;
this.name = name;
}
@Override
public void run() {
try {
for (int i = 0; i < 5; i++) {
broker.put(i);
System.out.format("%s produced: %s%n", name, i);
Thread.sleep(1000);
}
broker.put(-1);
System.out.println("produced termination signal");
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
public class Consumer implements Runnable {
private final Broker<Integer> broker;
private final String name;
public Consumer(Broker<Integer> broker, String name) {
this.broker = broker;
this.name = name;
}
@Override
public void run() {
try {
for (Integer message = broker.take(); message != -1; message = broker.take()) {
System.out.format("%s consumed: %s%n", name, message);
Thread.sleep(1000);
}
System.out.println("received termination signal");
} catch (InterruptedException e) {
e.printStackTrace();
return;
}
}
}
public class Main {
public static void main(String[] args) {
Broker<Integer> broker = new WaitNotifyBroker<Integer>(5);
// Broker<Integer> broker = new LockConditionBroker<Integer>(5);
// Broker<Integer> broker = new BlockingQueueBroker<Integer>();
new Thread(new Producer(broker, "prod 1")).start();
new Thread(new Consumer(broker, "cons 1")).start();
new Thread(new Consumer(broker, "cons 2")).start();
}
}
除了上述的方法,其实还有很多第三方的并发包可以解决这个问题。例如LMAX Disruptor和Chronicle等
本文完。
参考:
《Java 7 Concurrency Cookbook》
Java里的生产者-消费者模型(Producer and Consumer Pattern in Java)的更多相关文章
- 第23章 java线程通信——生产者/消费者模型案例
第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...
- 生产者和消费者模型producer and consumer(单线程下实现高并发)
#1.生产者和消费者模型producer and consumer modelimport timedef producer(): ret = [] for i in range(2): time.s ...
- Java实现多线程生产者消费者模型及优化方案
生产者-消费者模型是进程间通信的重要内容之一.其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案. /* 单生产者.单消费者生产烤鸭 ...
- java并发之生产者消费者模型
生产者和消费者模型是操作系统中经典的同步问题.该问题最早由Dijkstra提出,用以演示它提出的信号量机制. 经典的生产者和消费者模型的描写叙述是:有一群生产者进程在生产产品.并将这些产品提供给消费者 ...
- java多线程之生产者消费者模型
public class ThreadCommunication{ public static void main(String[] args) { Queue q = new Queue();//创 ...
- java多线程解决生产者消费者问题
import java.util.ArrayList; import java.util.List; /** * Created by ccc on 16-4-27. */ public class ...
- 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例
wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...
- java多线程:线程间通信——生产者消费者模型
一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...
- Java多线程14:生产者/消费者模型
什么是生产者/消费者模型 一种重要的模型,基于等待/通知机制.生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点: ...
随机推荐
- 使用Enyim.Caching访问阿里云的OCS
阿里云的开放式分布式缓存(OCS)简化了缓存的运维管理,使用起来很方便,官方推荐的.NET访问客户端类库为 Enyim.Caching,下面对此做一个封装. 首先引用最新版本 Enyim.Cachin ...
- CSS手动改变DIV高宽
本实例代码可以使DIV可以手动改变大小 效果体验:http://hovertree.com/code/css/resize.htm 代码如下: <!DOCTYPE html> <ht ...
- iOS笔记之NSSet
一.简介 NSSet到底什么类型,其实它和NSArray功能性质一样,用于存储对象,属于集合: NSSet , NSMutableSet类声明编程接口对象,无序的集合,在内存中存储方式是不连续的, ...
- Android系统的五种数据存储形式(二)
之前介绍了Android系统下三种数据存储形式,今天补充介绍另外两种,分别是内容提供者和网络存储.有些人可能认为内存提供者和网络存储更偏向于对数据的操作而不是数据的存储,但这两种方式确实与数据有关,所 ...
- 全文检索引擎 Solr 部署与基本原理
全文检索引擎 Solr 部署与基本原理 搜索引擎Solr环境搭建实例 关于 solr , schema.xml 的配置说明 全文检索引擎Solr系列-–全文检索基本原理 一.搜索引擎Solr环境搭建实 ...
- Xcode7--免证书真机调试
Xcode7之前,想要真机调试,必须花99刀购买开发者账号,而且步骤繁琐,需要下载证书.随着Xcode7的推出,大幅度的简化了真机调试的步骤,对ios开发工作者和正在学习ios开发的众多码农们,可以说 ...
- 关于UIApplication单例传值
由于UIApplication的是一个系统级别的单例,那么就能够省去自己创建单例的方法,将需要需要的类对象,在UIApplication单例内声明一个,通过点语法来实现单个 需要调用的实现单例模式的类 ...
- Redhat Server 5.7 安装配置PHP
PHP的简介 PHP于1994年由Rasmus Lerdorf创建,刚刚开始是Rasmus Lerdorf 为了要维护个人网页而制作的一个简单的用Perl语言编写的程序.这些工具程序用来显示 Rasm ...
- MongoDB学习笔记~MongoVUE对数据进行查询,排序和按需显示
回到目录 对于MongoDB这个非关系型数据库(NoSql)来说,找一个IDE工具不是很容易,还好被我找到了,它就是大名鼎鼎的MongoVUE,它可以对mongodb数据表进行增删改查,下面我主要说一 ...
- NGUI 指定视口大小
由于只是给Uinty开发插件,对Unity没有系统的学习,对Unity的很多功能都不是非常了解,幸得其他Unity同事的耐心教导,才不至于想崩头.记录一下,避免重复犯错. NGUI可以建立指定视口大小 ...