生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从仓库中取走产品。解决生产者/消费者问题的方法可分为两类:

(1)采用某种机制保护生产者和消费者之间的同步;

(2)在生产者和消费者之间建立一个管道。

第一种方式有较高的效率,并且易于实现,代码的可控制性较好,属于常用的模式。第二种管道缓冲区不易控制,被传输数据对象不易于封装等,实用性不强。因此本文只介绍同步机制实现的生产者/消费者问题

同步问题核心在于:如何保证同一资源被多个线程并发访问时的完整性。常用的同步方法是采用信号或加锁机制,保证资源在任意时刻至多被一个线程访问。Java语言在多线程编程上实现了完全对象化,提供了对同步机制的良好支持。在Java中一共有四种方法支持同步,其中前三个是同步方法,一个是管道方法。

(1)wait() / notify()方法

(2)await() / signal()方法

(3)BlockingQueue阻塞队列方法

(4)PipedInputStream / PipedOutputStream

本文先介绍第一种wait() / notify()方法,其他三种后面再讨论。

wait() / notify()方法

wait() / nofity()方法是基类Object的两个方法,也就意味着所有Java类都会拥有这两个方法,这样,我们就可以为任何对象实现同步机制。

wait()方法:当缓冲区已满/空时,生产者/消费者线程停止自己的执行,放弃锁,使自己处于等待状态,让其他线程执行。

notify()方法:当生产者/消费者向缓冲区放入/取出一个产品时,向其他等待的线程发出可执行的通知,同时放弃锁,使自己处于等待状态。

代码示例:

缓冲区(仓库):

public class Storage {
// 仓库最大存储量
private final int MAX_SIZE = 100; // 仓库存储的载体
private LinkedList<Object> list = new LinkedList<Object>(); // 生产num个产品
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个产品
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();//消费完后,释放锁,通知其他被阻塞的线程
}
} // get/set方法
public LinkedList<Object> getList(){
return list;
} public void setList(LinkedList<Object> list){
this.list = list;
} public int getMAX_SIZE(){
return MAX_SIZE;
}
}

生产者:

public class Producer extends Thread {
// 每次生产的产品数量
private int num; // 所在放置的仓库
private Storage storage; // 构造函数,设置仓库
public Producer(Storage storage){
this.storage = storage;
} // 线程run函数
public void run(){
produce(num);
} // 调用仓库Storage的生产函数
public void produce(int num){
storage.produce(num);
} // get/set方法
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;
} }

消费者:

public class Consumer extends Thread {
// 每次消费的产品数量
private int num; // 所在放置的仓库
private Storage storage; // 构造函数,设置仓库
public Consumer(Storage storage){
this.storage = storage;
} // 线程run函数
public void run(){
consume(num);
} // 调用仓库Storage的生产函数
public void consume(int num){
storage.consume(num);
} // get/set方法
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;
}
}

测试类:

public class Test {
public static void main(String[] args) {
// 仓库对象
Storage storage = new Storage(); // 生产者对象
Producer p1 = new Producer(storage);
Producer p2 = new Producer(storage);
Producer p3 = new Producer(storage);
Producer p4 = new Producer(storage);
Producer p5 = new Producer(storage);
Producer p6 = new Producer(storage);
Producer p7 = new Producer(storage); // 消费者对象
Consumer c1 = new Consumer(storage);
Consumer c2 = new Consumer(storage);
Consumer c3 = new Consumer(storage); // 设置生产者产品生产数量
p1.setNum(10);
p2.setNum(10);
p3.setNum(10);
p4.setNum(10);
p5.setNum(10);
p6.setNum(10);
p7.setNum(80); // 设置消费者产品消费数量
c1.setNum(50);
c2.setNum(20);
c3.setNum(30); // 线程开始执行
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}

你可能会对Storage类中为什么要定义public void produce(int num);和public void consume(int num);方法感到不解,为什么不直接在生产者类Producer和消费者类Consumer中实现这两个方法,却要调用Storage类中的实现呢?

因为如果在需要更新生产或者消费方法时,只需要更新仓库类Storage的代码即可,生产者Producer、消费者Consumer、测试类Test的代码均不需要进行任何更改。这样我们就知道为什么要在Storage类中定义public void produce(int num);和public void consume(int num);方法,并在生产者类Producer和消费者类Consumer中调用Storage类中的实现了吧。将可能发生的变化集中到一个类中,不影响原有的构架设计,同时无需修改其他业务层代码。无意之中,好像使用了一种叫做策略模式的设计模式!

生产者与消费者(一)---wait与notify的更多相关文章

  1. JAVA并发实现五(生产者和消费者模式wait和notify方式实现)

    package com.subject01; import java.util.PriorityQueue; /** * 通过wait和notify 实现 * 生产者-消费者模型:当队列满时,生产者需 ...

  2. 生产者与消费者模式-阻塞 wait,notify

    设计思路:生产者push ,消费者 拿,篮子装,syncstack先进后出,while 判断 index=0 wait,      当 Producer生产了 并push到篮子里  notify(唤醒 ...

  3. 【java线程系列】java线程系列之线程间的交互wait()/notify()/notifyAll()及生产者与消费者模型

    关于线程,博主写过java线程详解基本上把java线程的基础知识都讲解到位了,但是那还远远不够,多线程的存在就是为了让多个线程去协作来完成某一具体任务,比如生产者与消费者模型,因此了解线程间的协作是非 ...

  4. Thread(生产者和消费者) wait、notify、notifyAll

    在java中,线程间的通信可以使用wait.notify.notifyAll来进行控制.从名字就可以看出来这3个方法都是跟多线程相关的,但是可能让你感到吃惊的是:这3个方法并不是Thread类或者是R ...

  5. 母鸡下蛋实例:多线程通信生产者和消费者wait/notify和condition/await/signal条件队列

    简介 多线程通信一直是高频面试考点,有些面试官可能要求现场手写生产者/消费者代码来考察多线程的功底,今天我们以实际生活中母鸡下蛋案例用代码剖析下实现过程.母鸡在鸡窝下蛋了,叫练从鸡窝里把鸡蛋拿出来这个 ...

  6. java进阶(40)--wait与notify(生产者与消费者模式)

    文档目录: 一.概念 二.wait的作用 三.notify的作用 四.生产者消费者模式 五.举例 ---------------------------------------分割线:正文------ ...

  7. 第3月第2天 find symbolicatecrash 生产者-消费者 ice 引用计数

    1.linux find export find /Applications/Xcode.app/ -name symbolicatecrash -type f export DEVELOPER_DI ...

  8. java 22 - 19 多线程之生产者和消费者的代码优化

    在之前,是把生产者录入数据和消费者获取数据的所有代码都分别写在各自的类中. 这样不大好 这次把生产者和消费者部分关键代码都写入资源类中: package zl_Thread; public class ...

  9. 线程操作案例--生产者与消费者,Object类对线程的支持

    本章目标 1)加深对线程同步的理解 2)了解Object类中对线程的支持方法. 实例 生产者不断生产,消费者不断消费产品. 生产者生产信息后将其放到一个区域中,之后消费者从区域中取出数据. 既然生产的 ...

随机推荐

  1. java基础(十八)IO流(一)

    这里有我之前上课总结的一些知识点以及代码大部分是老师讲的笔记 个人认为是非常好的,,也是比较经典的内容,真诚的希望这些对于那些想学习的人有所帮助! 由于代码是分模块的上传非常的不便.也比较多,讲的也是 ...

  2. MVC 依赖注入/控制反转

    http://www.cnblogs.com/cnmaxu/archive/2010/10/12/1848735.html http://www.cnblogs.com/artech/archive/ ...

  3. bzoj 1513 [POI2006]Tet-Tetris 3D(二维线段树)

    1513: [POI2006]Tet-Tetris 3D Time Limit: 30 Sec  Memory Limit: 162 MBSubmit: 540  Solved: 175[Submit ...

  4. 5 approach to load UIView from Xib

    After the past few years I found that the only manageable way for creating/maintaining view (or any ...

  5. 机器学习笔记1——Introduction

    Introduction What is Machine Learning? Two definitions of Machine Learning are offered. Arthur Samue ...

  6. In Java, what is the default location for newly created files?

    If the current directory of the application. If e.g. you create a File by using new FileOutputStream ...

  7. redis 手册

    一.概述: 在该系列的前几篇博客中,主要讲述的是与Redis数据类型相关的命令,如String.List.Set.Hashes和Sorted-Set.这些命 令都具有一个共同点,即所有的操作都是针对与 ...

  8. Yii2 ActiveForm表单自定义样式

    实例: <?php $form = ActiveForm::begin([ 'fieldConfig' => [ 'template' => '<div class=" ...

  9. SQL 主键和外键约束

    SQL的主键和外键的作用: 外键取值规则:空值或参照的主键值. (1)插入非空值时,如果主键表中没有这个值,则不能插入. (2)更新时,不能改为主键表中没有的值. (3)删除主键表记录时,你可以在建外 ...

  10. JMeter入门(4):Java Request实例

    目的:对Java程序进行测试: 一.核心步骤 1.创建一个Java工程: 2.将JMeter的lib目录下的jar文件添加进此工程的Build Path: 3.创建一个类并实现JavaSamplerC ...