生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空、大小为 N 的缓冲区。只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待;只有缓冲区不空的时候,消费者才能从缓冲区取消息,否则必须等待。由于缓冲区是临界资源,在同一时间,它只允许一个生产者放入消息,或者一个消费者从中取出消息。

生产者消费者问题涉及到了多个线程通信的等待通知机制,生产者线程和消费者只能满足一定条件下才能操作共享的缓冲区,否则只能挂起等待,直到被唤醒重新竞争共享资源。Java API 提供了几个方法来实现这一机制:wait、notify、notifyAll。

wait: 在调用 wait 方法之前,线程必须获得该对象的对象级别锁。即只能在同步方法或同步块中调用 wait 方法,否则 JVM 会抛出 IllegalMonitorStateException 异常。在执行 wait 方法后,当前线程会释放锁。在 wait 方法返回之前,线程和其他线程重新竞争该对象锁。

notify: 在调用 notify 方法之前,线程同样须要获得该对象的对象级别锁,否则 JVM 会抛出 IllegalMonitorStateException 异常。notify 方法通知那些呈 wait 态的其他线程,如果有多个线程等待,则选择其中一个线程准备获得该对象锁。notify 方法执行完毕,呈 wait 状态的线程并不会立即获得该对象锁,须等到执行 notify 方法的线程执行完同步方法或同步块后,对象锁才会被释放,呈 wait 状态的线程才能获得锁。

notifyAll: notifyAll 与 notify 在于,notify 只唤醒一个等待锁的线程,而 notifyAll 会唤醒所有等待锁的线程。

生产者消费者问题示例

临界资源缓冲区

import java.util.ArrayList;
import java.util.List; /**
* 仓库
* @author huey
* @created 2016年10月19日
* @updated 2016年10月19日
* @version 1.0.0
*/
public class Storehouse { private List<Object> buffer;
private int maxSize; // 仓库最多允许存储 maxSize 个物品 public Storehouse(int maxSize) {
if (maxSize <= 0 || maxSize > 100) {
throw new IllegalArgumentException("maxSize 参数的值必须在 1 到 100之间");
}
this.maxSize = maxSize;
buffer = new ArrayList<Object>();
} public synchronized void add(Object data) {
if (buffer.size() == maxSize) {
throw new IndexOutOfBoundsException("仓库已经饱和,不能再输入物品。");
}
buffer.add(data);
System.out.printf("输入物品,仓库当前有 %d 个物品。\n", buffer.size());
} public synchronized void remove() {
buffer.remove(0);
System.out.printf("输出物品,仓库当前有 %d 个物品。\n", buffer.size());
} public boolean isEmpty() {
return buffer.isEmpty();
} public boolean isFull() {
return buffer.size() == maxSize;
}
}

生产者

import java.util.Random;

/**
* 生产者
* @author huey
* @created 2016年10月19日
* @updated 2016年10月19日
* @version 1.0.0
*/
public class Producer extends Thread { private Storehouse storehouse; public Producer(String name, Storehouse storehouse) {
super(name);
this.storehouse = storehouse;
} /**
* 生产者生产物品
* @author huey
* @created 2016年10月19日
* @updated 2016年10月19日
*/
public void produce() {
synchronized (storehouse) {
while (storehouse.isFull()) {
try {
System.out.printf("仓库当前处于饱和状态,生产者 %s 须挂起等待。\n", Thread.currentThread().getName());
storehouse.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} storehouse.add(new Object());
storehouse.notifyAll();
}
} @Override
public void run() {
for (int i = 0; i < 10; i++) {
this.produce();
try {
Thread.sleep(new Random().nextInt(50));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

消费者

import java.util.Random;

/**
* 消费者
* @author huey
* @created 2016年10月19日
* @updated 2016年10月19日
* @version 1.0.0
*/
public class Consumer extends Thread { private Storehouse storehouse; public Consumer(String name, Storehouse storehouse) {
super(name);
this.storehouse = storehouse;
} /**
* 消费者消费物品
* @author huey
* @created 2016年10月18日
* @updated 2016年10月18日
*/
public void consume() {
synchronized (storehouse) {
while (storehouse.isEmpty()) {
try {
System.out.printf("仓库当前处于空虚状态,消费者 %s 须挂起等待。\n", Thread.currentThread().getName());
storehouse.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} storehouse.remove();
storehouse.notifyAll();
}
} @Override
public void run() {
for (int i = 0; i < 5; i++) {
this.consume();
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}

测试用例

/**
*
* @author huey
* @created 2016年10月19日
* @updated 2016年10月19日
* @version 1.0.0
*/
public class Main {
public static void main(String[] args) {
Storehouse storehouse = new Storehouse(5); Thread[] producers = new Thread[] {
new Producer("P1", storehouse),
new Producer("P2", storehouse)
};
for (Thread producer : producers) {
producer.start();
} Thread[] consumers = new Thread[] {
new Consumer("C1", storehouse),
new Consumer("C2", storehouse),
new Consumer("C3", storehouse),
new Consumer("C4", storehouse)
};
for (Thread consumer : consumers) {
consumer.start();
}
}
}

运行结果:

输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输入物品,仓库当前有 2 个物品。
输出物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C2 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C4 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C1 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。
仓库当前处于空虚状态,消费者 C3 须挂起等待。
输入物品,仓库当前有 1 个物品。
输出物品,仓库当前有 0 个物品。

Java Concurrency - wait & notify, 等待通知机制的更多相关文章

  1. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  2. Java 线程间通信 —— 等待 / 通知机制

    本文部分摘自<Java 并发编程的艺术> volatile 和 synchronize 关键字 每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合 ...

  3. JMM之Java线程间通讯——等待通知机制及其经典范式

    在并发编程中,实际处理涉及两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体). 通信是指线程之间以何种机制来交换信息.在共享内存的并发模型里,线程之间共享程序的公共状 ...

  4. 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案

    前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...

  5. java并发编程实战《六》等待-通知机制

    用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...

  6. java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader

    1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...

  7. 二 Java利用等待/通知机制实现一个线程池

    接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1  定义一个任务的接口 ...

  8. Java多线程之三volatile与等待通知机制示例

    原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...

  9. Java并发编程,Condition的await和signal等待通知机制

    Condition简介 Object类是Java中所有类的父类, 在线程间实现通信的往往会应用到Object的几个方法: wait(),wait(long timeout),wait(long tim ...

随机推荐

  1. [置顶] 很荣幸被选为2013年度 CSDN博客之星评选,如果觉得我的文章可以,请投我一票!

    亲爱的小伙伴们,很荣幸我被选为<2013年度CSDN博客之星候选人>,希望大家多多支持,geekguy会继续努力,为大家奉献更好的文章. 投票地址:http://vote.blog.csd ...

  2. python list(列表)和tuple(元组)

    200 ? "200px" : this.width)!important;} --> 介绍 python中存在两种有序的类型列表,分别是list(列表)和tuple(元组) ...

  3. spark结合 Openfire服务器,发送聊天消息

    1.下载OpenFire服务器,进行安装,参考http://www.cnblogs.com/hoojo/archive/2012/05/17/2506769.html 2.程序运行客户端:下载客户端代 ...

  4. 应用XML作为数据库的快速开发框架

    背景 我经常应用C#开发一些小的桌面程序,这些桌面程序往往有以下几个特点: 程序比较小,开发周期很短. 程序的数据量不大,多数情况下不超过1万行记录. 对程序的性能要求不高. 程序并发很少或者基本没有 ...

  5. 在Android项目中使用AndroidAnnotations(配置框架,显示Hello World!)

    使用这个框架可以极大的简化在开发Android过程中的代码.提高开发的效率.这里简单说一下配置方式.和使用办法. 项目的地址为:AndroidAnnotations Jar包下载地址:3.0.1 下载 ...

  6. 【WinForm】C# 采用POST登录京东

    C# POST 传值登录 京东 想做一个DEMO 练练html的传值和接收,就用Winform 做了一个登录京东的程序. 首先参考的网址是: 艹蛋的青春じ 让我蛋疼ミ:http://www.cnblo ...

  7. javascript常见编程模式举例

    近期买到手了一本<javascript框架设计>,具体介绍开发js框架所用到的知识.初读一点,乐帝脆弱的理论修养就暴露无遗了,所以专门加强理论修养,重看javascript编程模式的举例. ...

  8. U盘安装Win7 64位系统(笔记本+台式机亲测)

    准备工具: 1. Win7 64位系统的镜像文件(网上随便一搜即可,最好是纯净版,没有一堆乱七八糟的内置软件) 2. 4G以上的U盘一个 所用软件: 老毛桃(官网下载) 具体步骤: 1.数据备份(将原 ...

  9. 有图有真相,分享一款网页版HTML5飞机射击游戏

    本飞机射击游戏是使用HTML5代码写的,尝试通过统一开发环境(UDE)将游戏托管在MM应用引擎,直接生成了网页版游戏,游戏简单易上手,非常适合用来当做小休闲打发时间. 游戏地址:http://flyg ...

  10. iOS开发——网络Swift篇&NSURL进行数据请求(POST与GET)

    NSURL进行数据请求(POST与GET)   使用Swift进行iOS开发时,不可避免的要进行远程的数据获取和提交. 其数据请求的方式既可能是POST也可能是GET.同不管是POST还是GET又可以 ...