生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空、大小为 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. javascript中document对象的属性和方法

    document.documentElement; document.firstChild;document.childNodes[0];// 取得对<html>的引用document.b ...

  2. Windows 环境下于虚拟环境安装源码安装 cx_oracle

    安装前提条件: (1).安装 instantclient-basic-nt (2).安装 instantclient-sdk-nt (3).安装 Microsoft Visual C++ Compil ...

  3. iOS UIWebView加载时添加进度条

    标注:此框架仅适合UIWebView  对iOS8后新出的WKWebView不适用,当然,你可以尝试修改框架里的几个代理方法. 框架是:NJKWebViewProgress 导入头文件 #import ...

  4. [置顶] Quartz的DateBuilder详解

    DateBuilder类有两个方法: nextGivenMinuteDate和nextGivenSecondDate: Method: (a)  public static  Date   nextG ...

  5. Apache POI 合并单元格

    合并单元格所使用的方法: sheet.addMergedRegion( CellRangeAddress  cellRangeAddress  );   CellRangeAddress  对象的构造 ...

  6. skyline TerraExplorer fly设置相对路径的方法

    软件环境:TerraExplorer Pro(以下简称TEP)6.5 在TEP中,对于本地(非网络)文件路径,默认都是绝对路径,在移动数据时非常麻烦,以下是本人总结出一些设置相对路径的规则 假设fly ...

  7. Java模拟网站登录

    web登陆无非就是网页获取,cookie 的管理,post和get方式的模拟. 1.网页内容获取 java.io.InputStream in; java.net.URL url = new java ...

  8. Codeforces Round #330 (Div. 2) A. Vitaly and Night 暴力

    A. Vitaly and Night Time Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/contest/595/p ...

  9. sql语句having子句用法,很多时候你曾忘掉

    显示每个地区的总人口数和总面积.仅显示那些面积超过1000000的地区. SELECT region, SUM(population), SUM(area) FROM bbc GROUP BY reg ...

  10. 读logback源码系列文章(五)——Appender --转载

    原文地址:http://kyfxbl.iteye.com/blog/1173788 明天要带老婆出国旅游几天,所以这段时间暂时都更新不了博客了,临走前再最后发一贴 上一篇我们说到Logger类的inf ...