java 多线程并发系列之 生产者消费者模式的两种实现

在并发编程中使用生产者和消费者模式能够解决绝大多数并发问题。该模式通过平衡生产线程和消费线程的工作能力来提高程序的整体处理数据的速度。

为什么要使用生产者和消费者模式

在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程。在多线程开发当中,如果生产者处理速度很快,而消费者处理速度很慢,那么生产者就必须等待消费者处理完,才能继续生产数据。同样的道理,如果消费者的处理能力大于生产者,那么消费者就必须等待生产者。为了解决这种生产消费能力不均衡的问题,所以便有了生产者和消费者模式。

什么是生产者消费者模式

生产者消费者模式是通过一个容器来解决生产者和消费者的强耦合问题。生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理,直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力。

这个阻塞队列就是用来给生产者和消费者解耦的。纵观大多数设计模式,都会找一个第三者出来进行解耦,如工厂模式的第三者是工厂类,模板模式的第三者是模板类。在学习一些设计模式的过程中,如果先找到这个模式的第三者,能帮助我们快速熟悉一个设计模式。

产者/消费者模型描述如下:

有一个或多个生产者生产某种类型的数据,并放置缓冲区(可以是数组也可以是队列等数据结构)中;

有一个消费者可以从缓冲区中取数据,每次取一项;

系统保证避免对缓冲区的重复操作,也就是说在任何时候只有一个主体(生产者或消费者)可以访问缓冲区。

问题要确保缓冲区不溢出,即当缓冲区满时,生成者不会继续向其中添加数据;当缓冲区空时,消费者不会从中移走数据。

生产者消费者模式是并发、多线程编程中经典的设计模式,生产者和消费者通过分离的执行工作解耦,简化了开发模式,生产者和消费者可以以不同的速度生产和消费数据。

真实世界中的生产者消费者模式

生产者和消费者模式在生活当中随处可见,它描述的是协调与协作的关系。比如一个人正在准备食物(生产者),而另一个人正在吃(消费者),他们使用一个共用的桌子用于放置盘子和取走盘子,生产者准备食物,如果桌子上已经满了就等待,消费者(那个吃的)等待如果桌子空了的话。这里桌子就是一个共享的对象。

生产者与消费者模型中,要保证以下几点:
1 同一时间内只能有一个生产者生产
2 同一时间内只能有一个消费者消费
3 生产者生产的同时消费者不能消费
4 消费者消费的同时生产者不能生产
5 共享空间空时消费者不能继续消费
6 共享空间满时生产者不能继续生产

我们看这样一个例子:

生产者:  往一个公共的盒子里面放苹果

消费者:从公共的盒子里面取苹果

盒子:盒子的容量不能超过5

下面我们用两者方法分别实现这样一个场景。

方法一:   wait()  和   notify()   通信方法实现

package 生产者消费者;

/** 生产者与消费者模型中,要保证以下几点:
 * 1 同一时间内只能有一个生产者生产
 * 2 同一时间内只能有一个消费者消费
 * 3 生产者生产的同时消费者不能消费
 * 4 消费者消费的同时生产者不能生产
 * 5 共享空间空时消费者不能继续消费
 * 6 共享空间满时生产者不能继续生产
 *
 * */

public class PublicBox {
    private int apple = 0;
    public synchronized void increace() {
        while (apple == 5) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        apple++;
        System.out.println("生产了一个");
        notify();
    }
    public synchronized void decreace() {
        while (apple ==0) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        apple--;
        System.out.println("消费了一个");
        notify();
    } 

    public static void main(String []args)
       {
              PublicBox box= new PublicBox();

              Consumer con= new Consumer(box);
              Producer pro= new Producer(box);

              Thread t1= new Thread(con);
              Thread t2= new Thread(pro);

              t1.start();
              t2.start();

       }
}

//生产者代码(定义十次):
class Producer implements Runnable {
    private PublicBox box; 

    public Producer(PublicBox box) {
        this .box = box;
    } 

    @Override
    public void run() {
        for( int i=0;i<10;i++)
        {
            box.increace();
        }
    }
}

//消费者代码(同样十次):
class Consumer implements Runnable {
    private PublicBox box; 

    public Consumer(PublicBox box) {
        this .box = box;
    }
    @Override
    public void run() {
         for( int i=0;i<10;i++)
        {
             box.decreace();
         }
    }
}

输出如下:

生产了一个
生产了一个
生产了一个
生产了一个
消费了一个
消费了一个
消费了一个
消费了一个
生产了一个
生产了一个
生产了一个
生产了一个
生产了一个
消费了一个
消费了一个
消费了一个
消费了一个
消费了一个
生产了一个
消费了一个


方法二:采用阻塞队列实现生产者消费者模式

阻塞队列实现生产者消费者模式超级简单,它提供开箱即用支持阻塞的方法put()和take(),开发者不需要写困惑的wait-nofity代码去实现通信。BlockingQueue 一个接口,Java5提供了不同的现实,如ArrayBlockingQueue和LinkedBlockingQueue,两者都是先进先出(FIFO)顺序。而ArrayLinkedQueue是自然有界的,LinkedBlockingQueue可选的边界。下面这是一个完整的生产者消费者代码例子,对比传统的wait、nofity代码,它更易于理解。

package 生产者消费者2;

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

public class PublicBoxQueue {

        public static void main(String []args)
       {
            //定义了一个大小为5的盒子
               BlockingQueue publicBoxQueue= new LinkedBlockingQueue(5); 

              Thread pro= new Thread(new Producer(publicBoxQueue));
              Thread con= new Thread(new Consumer(publicBoxQueue));

              pro.start();
              con.start();
       }

}

//生产者
class Producer implements Runnable {
        private final BlockingQueue proQueue;

        public Producer(BlockingQueue proQueue)
       {
               this .proQueue =proQueue;
       }
        @Override
        public void run() {
               // TODO Auto-generated method stub
               for (int i=0;i<10;i++)
              {
                      try {
                           System. out .println("生产者生产的苹果编号为 : " +(i+1));  //放入十个苹果编号 为1到10
                           proQueue .put(i+1);
                            //Thread.sleep(3000);
                     } catch (InterruptedException  e) {
                            // TODO: handle exception
                           e.printStackTrace();
                     }
              }
       }   

}

//消费者
class Consumer implements Runnable {
        private final BlockingQueue conQueue;   

        public Consumer(BlockingQueue conQueue)
       {
               this .conQueue =conQueue;
       }
        @Override
        public void run() {
               // TODO Auto-generated method stub
               for (int i=0;i<10;i++)
              {
                      try {
                           System. out .println("消费者消费的苹果编号为 :" +conQueue .take());
                           //Thread. sleep(3000);  //在这里sleep是为了看的更加清楚些

                     } catch (InterruptedException e) {
                            // TODO: handle exception
                           e.printStackTrace();
                     }
              }
       }

}

结果如下:

生产者生产的苹果编号为 : 1
生产者生产的苹果编号为 : 2
生产者生产的苹果编号为 : 3
消费者消费的苹果编号为 :1
消费者消费的苹果编号为 :2
消费者消费的苹果编号为 :3
生产者生产的苹果编号为 : 4
生产者生产的苹果编号为 : 5
消费者消费的苹果编号为 :4
消费者消费的苹果编号为 :5
生产者生产的苹果编号为 : 6
生产者生产的苹果编号为 : 7
生产者生产的苹果编号为 : 8
生产者生产的苹果编号为 : 9
生产者生产的苹果编号为 : 10
消费者消费的苹果编号为 :6
消费者消费的苹果编号为 :7
消费者消费的苹果编号为 :8
消费者消费的苹果编号为 :9
消费者消费的苹果编号为 :10

生产者消费者模式的好处

它的确是一种实用的设计模式,常用于编写多线程或并发代码。下面是它的一些优点:

  1. 它简化的开发,你可以独立地或并发的编写消费者和生产者,它仅仅只需知道共享对象是谁
  2. 生产者不需要知道谁是消费者或者有多少消费者,对消费者来说也是一样
  3. 生产者和消费者可以以不同的速度执行
  4. 分离的消费者和生产者在功能上能写出更简洁、可读、易维护的代码
时间: 2024-10-26 01:35:21

java 多线程并发系列之 生产者消费者模式的两种实现的相关文章

java中多线程通信实例:生产者消费者模式

线程间的通信: 其实就是多个线程再操作同一个资源,但是操作的动作不同   当某个线程进入synchronized块后,共享数据的状态不一定满足该线程的需要,需要其他线程改变共享数据的状态后才能运行,而由于当时线程对共享资源时独占的,它必须解除对共享资源的锁定的状态,通知其他线程可以使用该共享资源. Java中的 wait(),notify(),notifyAll()可以实现线程间的通信. 生产者--消费者问题是典型的线程同步和通信问题 /** * 生产者和消费者问题,生产者生成出产品,消费者去购

Java多线程-----实现生产者消费者模式的几种方式

   1 生产者消费者模式概述 生产者消费者模式就是通过一个容器来解决生产者和消费者的强耦合问题.生产者和消费者彼此之间不直接通讯,而通过阻塞队列来进行通讯,所以生产者生产完数据之后不用等待消费者处理, 直接扔给阻塞队列,消费者不找生产者要数据,而是直接从阻塞队列里取,阻塞队列就相当于一个缓冲区,平衡了生产者和消费者的处理能力.这个阻塞队列就是用来给生产者和消费者解耦的. 在线程世界里,生产者就是生产数据的线程,消费者就是消费数据的线程.在多线程开发当中,如果生产者处理速度很快,而消费者处理速度

Java 多线程学习笔记:生产者消费者问题

前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后,自行实现阻塞队列. (一)准备 在多线程中,生产者-消费者问题是一个经典的多线程同步问题.简单来说就是有两种线程(在这里也可以做进程理解)——生产者和消费者,他们共享一个固定大小的缓存区(如一个队列).生产者负责产生放入新数据,消费者负责取出缓存区的数据.具体介绍请参考 Producer-consumer

多线程设计模式:Producer-Consumer生产者-消费者模式的C++

我们这里介绍的Producer-Consumer生产者-消费者模式是多线程设计模式中很著名的一个设计模式.说到生产者消费者问题,大部分人都不会陌生,OS课的经典问题,并且其本身就是一个计算机编程中常见的问题.对于它的应用,可以举出无数的例子,小到一个多线程程序对队列的共享互斥操作,大到目前流行的中间件产品,诸如BEA的BMQ(BEA Message Queue),IBM的MQ Serious等中间件就是将生产者消费者问题应用通用化体系化的结果. 实际上,生产者消费者模式跟我们之前的多线程设计模式

java多线程并发系列之锁的深入了解

上一篇博客中 : java多线程.并发系列之 (synchronized)同步与加锁机制 .介绍了java中Synchronized和简单的加锁机制,在加锁的模块中介绍了 轮询锁和定时锁,简单回顾下 轮询锁:利用tryLock来获取两个锁,如果不能同时获得,那么回退并重新尝试. 定时锁:索取锁的时候可以设定一个超时时间,如果超过这个时间还没索取到锁,则不会继续堵塞而是放弃此次任务. 锁的公平性 在公平的锁上,线程将按照它们发出请求的顺序来获取锁 上面似乎忘记了还有一种可中断锁和可选择粒度锁 可中

java多线程并发系列之闭锁(Latch)和栅栏(CyclicBarrier)

-闭锁(Latch) 闭锁(Latch):一种同步方法,可以延迟线程的进度直到线程到达某个终点状态.通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态.也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成. 应用场景: 确保某个计算在其需要的所有资源都被初始化之后才继续执行.二元闭锁(包括两个状态)可以用来表

生产者消费者模式的三种实现方式

synchronized版本 public class Test { public static void main(String[] args) { Shared s = new Shared(); Thread t1 = new Thread(new Product(s)); Thread t2 = new Thread(new Consumer(s)); t1.start(); t2.start(); } } class Product implements Runnable { priv

关于生产者与消费者模式的两种实现方式

1.wait & notify 方法: public class WaitAndNotify { private final int MAX_SIZE = 100; private LinkedList<Object> list = new LinkedList<Object>(); public void produce(int num) { synchronized (list) { while (list.size() + num > MAX_SIZE) { S

11.9-全栈Java笔记: 线程并发协作(生产者/消费者模式)

多线程环境下,我们经常需要多个线程的并发和协作.这个时候,就需要了解一个重要的多线程并发协作模型"生产者消费者模式". 什么是生产者? 生产者指的是负责生产数据的模块(这里模块可能是:方法.对象.线程.进程). 什么是消费者? 消费者指的是负责处理数据的模块(这里模块可能是:方法.对象.线程.进程). 什么是缓冲区? 消费者不能直接使用生产者的数据,它们之间有个"缓冲区".生产者将生产好的数据放入"缓冲区",消费者从"缓冲区"