生产者-消费者模型是多线程问题里面的经典问题,也是面试的常见问题。有如下几个常见的实现方法:

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)的更多相关文章

  1. 第23章 java线程通信——生产者/消费者模型案例

    第23章 java线程通信--生产者/消费者模型案例 1.案例: package com.rocco; /** * 生产者消费者问题,涉及到几个类 * 第一,这个问题本身就是一个类,即主类 * 第二, ...

  2. 生产者和消费者模型producer and consumer(单线程下实现高并发)

    #1.生产者和消费者模型producer and consumer modelimport timedef producer(): ret = [] for i in range(2): time.s ...

  3. Java实现多线程生产者消费者模型及优化方案

    生产者-消费者模型是进程间通信的重要内容之一.其原理十分简单,但自己用语言实现往往会出现很多的问题,下面我们用一系列代码来展现在编码中容易出现的问题以及最优解决方案. /* 单生产者.单消费者生产烤鸭 ...

  4. java并发之生产者消费者模型

    生产者和消费者模型是操作系统中经典的同步问题.该问题最早由Dijkstra提出,用以演示它提出的信号量机制. 经典的生产者和消费者模型的描写叙述是:有一群生产者进程在生产产品.并将这些产品提供给消费者 ...

  5. java多线程之生产者消费者模型

    public class ThreadCommunication{ public static void main(String[] args) { Queue q = new Queue();//创 ...

  6. java多线程解决生产者消费者问题

    import java.util.ArrayList; import java.util.List; /** * Created by ccc on 16-4-27. */ public class ...

  7. 如何在 Java 中正确使用 wait, notify 和 notifyAll – 以生产者消费者模型为例

    wait, notify 和 notifyAll,这些在多线程中被经常用到的保留关键字,在实际开发的时候很多时候却并没有被大家重视.本文对这些关键字的使用进行了描述. 在 Java 中可以用 wait ...

  8. java多线程:线程间通信——生产者消费者模型

    一.背景 && 定义 多线程环境下,只要有并发问题,就要保证数据的安全性,一般指的是通过 synchronized 来进行同步. 另一个问题是,多个线程之间如何协作呢? 我们看一个仓库 ...

  9. Java多线程14:生产者/消费者模型

    什么是生产者/消费者模型 一种重要的模型,基于等待/通知机制.生产者/消费者模型描述的是有一块缓冲区作为仓库,生产者可将产品放入仓库,消费者可以从仓库中取出产品,生产者/消费者模型关注的是以下几个点: ...

随机推荐

  1. Linux(CentOS 6.7)下配置Mono和Jexus并且部署ASP.NET MVC3、4、5和WebApi(跨平台)

    1.开篇说明 a. 首先我在写这篇博客之前,已经在自己本地配置了mono和jexus并且成功部署了asp.net mvc项目,我也是依赖于在网上查找的各种资料来配置环境并且部署项目的,而其在网上也已有 ...

  2. 分享15款很实用的 Sass 和 Compass 工具

    Sass 是 CSS 的扩展,增加了嵌套规则,变量,混入功能等很多更多.它简化了组织和维护 CSS 代码的成本.Compass 是一个开源的 CSS 框架,使得使用 CSS3 和流行的设计模式比以往任 ...

  3. 基于HTML5 Canvas实现的图片马赛克模糊特效

    效果请点击下面网址: http://hovertree.com/texiao/html5/1.htm 一.开门见山受美国肖像画家Chuck Close的启发,此脚本通过使用HTML5 canvas元素 ...

  4. [读码][js,css3]能感知鼠标方向的图片遮罩效果

    效果图: 无意间看到过去流行的一个效果:[能感知鼠标方向的图片遮罩效果]近来不忙,就仔细的看了一看看到后来发现,网上有好多版本,谁是原著者似乎已经无法考证.读码就要读比较全面的,读像是原著的代码.代码 ...

  5. JavaScript异步编程(2)- 先驱者:jsDeferred

    JavaScript当前有众多实现异步编程的方式,最为耀眼的就是ECMAScript 6规范中的Promise对象,它来自于CommonJS小组的努力:Promise/A+规范. 研究javascri ...

  6. Linux学习心得之 Linux下命令行Android开发环境的搭建

    作者:枫雪庭 出处:http://www.cnblogs.com/FengXueTing-px/ 欢迎转载 Linux学习心得之 Linux下命令行Android开发环境的搭建 1. 前言2. Jav ...

  7. 源代码管理工具之SVN

    源代码管理工具SVN是一款非常强大的源代码管理工具,现在国内70%-90%的公司都在使用SVN来管理源代码,下面就让小编给大家着重介绍一下SVN的使用,SVN的使用主要分为下面几块. SVN的使用环境 ...

  8. 详解tintColor属性

    tintColor属性是iOS7之后新加的一个属性,这个属性定义了一个非默认的着色颜色值,其值的设置会影响到以视图为根视图的整个视图层次结构.它主要是改变控件的颜色,以获取一些有意思的视觉效果. ti ...

  9. Maven之 聚合与继承 详解

    说到聚合与继承我们都很熟悉,maven同样也具备这样的设计原则,下面我们来看一下Maven的pom如何进行聚合与继承的配置实现. 一.为什么要聚合? 随着技术的飞速发展和各类用户对软件的要求越来越高, ...

  10. Play Framework 完整实现一个APP(七)

    1.添加验证码 Application Controller添加captcha() public static void captcha() { Images.Captcha captcha = Im ...