生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空、大小为 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. CodeForces 705B Spider Man (水题)

    题意:给定 n 个数,表示不同的环,然后把环拆成全是1,每次只能拆成两个,问你有多少次. 析:也不难,反正都要变成1,所以把所有的数都减1,再求和即可. 代码如下: #pragma comment(l ...

  2. python crawler0723.py

    #!/usr/env  python #-*- coding: utf-8  -*- import urllib import urllib2 import random import request ...

  3. web config数据库连接字符串加密

    ASP.NET web.config中,数据库连接字符串的加密与解密 ASP.NET web.config中,数据库连接字符串的加密与解密. 开始--->运行,输入cmd,接着输入以下内容 加密 ...

  4. 连接查询中on and和on where的区别

    一.区别 1. and条件是在生成临时表时使用的条件,它不管and中的条件是否为真,都会返回左(或者右)边表中的记录. 2.where条件是在临时表生成好后,再对临时表进行过滤的条件.这时已经没有le ...

  5. PostgreSQL的 initdb 源代码分析之十六

    继续分析 setup_description(); 展开后: 就是要把 share/postgres.description 文件的内容读入到 pg_description 和 pg_shdescri ...

  6. java连接数据库的解决方法大全(mysql)

    解决方法一:(最重要的一种方法) 你看下my.ini,有无  [mysql] default-character-set=utf8 [client]  default-character-set=ut ...

  7. Codeforces Gym 100571A A. Cursed Query 离线

    A. Cursed QueryTime Limit: 20 Sec Memory Limit: 256 MB 题目连接 http://codeforces.com/gym/100571/problem ...

  8. 常见AutoCAD病毒(acad.fas、acad.lsp)清除方法

    常见AutoCAD病毒(acad.fas.acad.lsp)清除方法 acad.fas.acad.lsp这两种病毒是最常见的CAD病毒了,而且往往同一时候出现.因为其本身对系统并不具备危害性,不过恶作 ...

  9. StarlingMVC Framework 原理。。。

    向starlingmvc 中添加bean后..会根据Metadata标签,分别交给不同的Processor去处理...然后会执行每个bean的postConstruct函数.相当于初始化函数...可以 ...

  10. 在Linux里设置环境变量的方法(export PATH)

    一般来说,配置交叉编译工具链的时候需要指定编译工具的路径,此时就需要设置环境变量.例如我的mips-linux-gcc编译器在“/opt/au1200_rm/build_tools/bin”目录下,b ...