Java Concurrency - wait & notify, 等待通知机制
生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空、大小为 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, 等待通知机制的更多相关文章
- java多线程系列(三)---等待通知机制
等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...
- Java 线程间通信 —— 等待 / 通知机制
本文部分摘自<Java 并发编程的艺术> volatile 和 synchronize 关键字 每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合 ...
- JMM之Java线程间通讯——等待通知机制及其经典范式
在并发编程中,实际处理涉及两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体). 通信是指线程之间以何种机制来交换信息.在共享内存的并发模型里,线程之间共享程序的公共状 ...
- 【Java并发基础】使用“等待—通知”机制优化死锁中占用且等待解决方案
前言 在前篇介绍死锁的文章中,我们破坏等待占用且等待条件时,用了一个死循环来获取两个账本对象. // 一次性申请转出账户和转入账户,直到成功 while(!actr.apply(this, targe ...
- java并发编程实战《六》等待-通知机制
用"等待-通知"机制优化循环等待 前言 在破坏占用且等待条件的时候,如果转出账本和转入账本不满足同时在文件架上这个条件,就用死循环的方式来循环等待. 1 // 一次性申请转出账户和 ...
- java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader
1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...
- 二 Java利用等待/通知机制实现一个线程池
接着上一篇博客的 一Java线程的等待/通知模型 ,没有看过的建议先看一下.下面我们用等待通知机制来实现一个线程池 线程的任务就以打印一行文本来模拟耗时的任务.主要代码如下: 1 定义一个任务的接口 ...
- Java多线程之三volatile与等待通知机制示例
原子性,可见性与有序性 在多线程中,线程同步的时候一般需要考虑原子性,可见性与有序性 原子性 原子性定义:一个操作或者多个操作在执行过程中要么全部执行完成,要么全部都不执行,不存在执行一部分的情况. ...
- Java并发编程,Condition的await和signal等待通知机制
Condition简介 Object类是Java中所有类的父类, 在线程间实现通信的往往会应用到Object的几个方法: wait(),wait(long timeout),wait(long tim ...
随机推荐
- SMTP邮件服务器配置
QQ个人邮箱使用: smtp.qq.com端口为25 密码是个人邮箱密码 QQ企业邮箱使用: smtp.exmail.qq.com端口为25 密码是邮箱密码 163邮箱使用 smtp.163.com端 ...
- git 入门学习笔记
安装msysgit (模拟环境Git)安装后绑定邮箱和名字$ git config --global user.name "Your Name"$ git config --glo ...
- Unity3D之移植学习笔记:移植到Android平台更好的方法
接上文,之前我们采用了直接将Unity项目导出为Eclipse项目来修改的方式,这种做法存在的一个最大的问题就是:每当Unity被修改之后,都需要重新导出,而每次导出的项目在Android平台方面的J ...
- .Net 揭密--JIT怎样运行你的代码
方法调用: 第一部分 (普通调用) 译者:我们都知道.NET托管代码如C#.VB.NET写成的代码,都是先被编译成中间语言(IL,Intermediate Language,在运行时,再由即时编译器( ...
- Objective-C :Category
Category 引入 在日常的开发中,可能会碰到这样的需求:给某个类增加方法.比如说,需要给NSString类增加一个打印的方法.当然,我们可以新建一个类比如TestString,并继承NSStri ...
- 红帽 Enterprise Linux OpenStack Platform 4.0全面上市
十一月,红帽公司推出Red Hat Enterprise Linux OpenStack Platform 4.0测试版,这款企业级解决方案集Red Hat Enterprise Linux的稳定性与 ...
- Python3爬取中文网址
本以为Python3的中文兼容性无比友好,结果还是会有一点问题的. 解决方案如下,就以爬取我自己的一个网页极简翻译为例 #coding:utf8 import urllib.request impor ...
- idea 搭建java项目
IntelliJ IDEA 12.0搭建Maven Web SSH2架构项目示例 以IDEA为环境,搭建SSH架构示例程序,用Maven管理依赖.这篇文章是一个示例,你需要首先搭建好M ...
- MEF 编程指南(五):延迟导出
在组合部件的时候,导入将会触发部件(部件集合)的实例化,为原始的请求部件公开必要的导出需求.对于有些应用程序,推迟实例化 - 并且防止递归组合图(Recursive Composition Down ...
- contiki-main.c 中的process系列函数学习笔记 <contiki学习笔记之六>
说明:本文依然依赖于 contiki/platform/native/contiki-main.c 文件. ---------------------------------------------- ...