一.概述

一个线程修改了一个对象的值,另一个线程感知到变化从而做出相应的操作。前者是生产者,后者是消费者。

等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()或者notifyAll()方法,线程A收到通知后从对象O的wait()方法返回,进而执行后续操作。上述两个线程通过对象O来完成交互,而对象上的wait()和notify/notifyAll()的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。

二.代码示例

package concurrent;

import java.util.concurrent.TimeUnit;

public class WaitNotifyDemo2 {

    static final Object obj = new Object();
private static boolean flag = false; public static void main(String[] args) throws InterruptedException {
Thread produce = new Thread(new Produce(), "生产者线程");
Thread consume = new Thread(new Consume(), "消费者线程");
consume.start();
Thread.sleep(1000);
produce.start(); } //生产者线程
static class Produce implements Runnable { @Override
public void run() {
//加锁,拥有lock的monitor
synchronized (obj) {
System.out.println("进入生产者线程");
System.out.println("生产");
try {
//模拟生产
TimeUnit.MILLISECONDS.sleep(2000);
//改变条件
flag = true;
// 获取lock的锁,然后进行通知等待在obj上的对象,通知时不会释放lock的锁,
// 直到当前线程释放了lock后,WaitThread才能从wait方法中返回
obj.notifyAll();
TimeUnit.MILLISECONDS.sleep(1000); //模拟其他耗时操作
System.out.println("退出生产者线程");
} catch (InterruptedException e) {
e.printStackTrace();
} }
} } //消费者线程
static class Consume implements Runnable { @Override
public void run() {
// 加锁,拥有lock的Monitor
synchronized (obj) {
System.out.println("进入消费者线程");
System.out.println("wait flag 1:" + flag);
// 当条件不满足时,继续wait,同时释放了lock的锁
while (! flag) {
System.out.println("还没生产,进入等待");
try {
//等待,线程被阻塞
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束等待");
} //满足条件,消费
System.out.println("wait flag 2:" + flag);
System.out.println("消费");
System.out.println("退出消费者线程");
}
} }
}

打印如下:

进入消费者线程
wait flag 1:false
还没生产,进入等待
进入生产者线程
生产
退出生产者线程
结束等待
wait flag 2:true
消费
退出消费者线程

对这一过程的详解:

在图4-3中,WaitThread首先获取了对象的锁,然后调用对象的wait()方法,从而放弃了锁并进入了对象的等待队列WaitQueue中,进入等待状态。由于WaitThread释放了对象的锁,NotifyThread随后获取了对象的锁,并调用对象的notify()方法,将WaitThread从WaitQueue移到SynchronizedQueue中,此时WaitThread的状态变为阻塞状态。NotifyThread释放了锁之后,WaitThread再次获取到锁并从wait()方法返回继续执行。

三.等待/通知机制范式的提炼

等待方遵循如下原则。

1)获取对象的锁。

2)如果条件不满足,那么调用对象的wait()方法,被通知后仍要检查条件。

3)条件满足则执行对应的逻辑。

对应的伪代码如下。

synchronized(对象) {
while(条件不满足) {
对象.wait();
}
对应的处理逻辑
}

通知方遵循如下原则。

1)获得对象的锁。

2)改变条件。

3)通知所有等待在对象上的线程。

对应的伪代码如下。

synchronized(对象) {
改变条件
对象.notifyAll();
}

四.为什么要使用while循环/notifyAll

1.为什么用while:wait()的线程永远不能确定其他线程会在什么状态下notify(),所以必须在被唤醒、抢占到锁并且从wait()方法退出的时候再次进行指定条件的判断,以决定是满足条件往下执行呢还是不满足条件再次wait()呢?多个线程消费,那么就极有可能出现唤醒生产者的是另一个生产者或者唤醒消费者的是另一个消费者,这样的情况下用if就必然会现类似过度生产或者过度消费的情况了

2.使用notifyAll而不是notify

notify适用于只有一个生产者和一个消费者的情况,如果是两个生产者两个消费者,notify()导致死锁,需使用notifyAll()

Java并发编程的艺术笔记(二)——wait/notify机制的更多相关文章

  1. Java并发编程实战 读书笔记(二)

    关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...

  2. 多线程的通信和同步(Java并发编程的艺术--笔记)

    1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递.   2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...

  3. Java并发编程的艺术笔记(七)——CountDownLatch、CyclicBarrier详解

    一.等待多线程完成的CountDownLatch CountDownLatch允许一个或多个线程等待其他线程完成操作,像加强版的join.(t.join()是等待t线程完成) 例: (1)开启多个线程 ...

  4. Java并发编程的艺术笔记(五)——Java中的锁

    一.Lock接口的几个功能: 显示的获取和释放锁 尝试非阻塞的获取锁 能被中断的获取锁 超时获取锁 使用方式: Lock lock = new ReentrantLock(); lock.lock() ...

  5. Java并发编程的艺术笔记(九)——FutureTask详解

    FutureTask是一种可以取消的异步的计算任务.它的计算是通过Callable实现的,多用于耗时的计算. 一.FutureTask的三种状态 二.get()和cancel()执行示意 三.使用 一 ...

  6. Java并发编程的艺术(十二)——并发容器和框架

    ConcurrentHashMap 为什么需要ConcurrentHashMap HashMap线程不安全,因为HashMap的Entry是以链表的形式存储的,如果多线程操作可能会形成环,那样就会死循 ...

  7. Java并发编程的艺术· 笔记(1)

    目录 1.volatile的原理 2.Synchonized 3.无锁-偏向锁-轻量级锁-重量级锁 4.Java实现原子操作 1.volatile的原理 如何保持可见性: 1)将当前处理器缓存行的数据 ...

  8. Java并发编程的艺术(十二)——线程安全

    1. 什么是『线程安全』? 如果一个对象构造完成后,调用者无需额外的操作,就可以在多线程环境下随意地使用,并且不发生错误,那么这个对象就是线程安全的. 2. 线程安全的几种程度 线程安全性的前提:对『 ...

  9. Java并发编程的艺术笔记(八)——线程池

    一.线程池的主要处理流程 ThreadPoolExecutor执行execute方法分下面4种情况. 1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步需要获 ...

随机推荐

  1. PAT B1021 个位数统计 (15)

    AC代码 #include <cstdio> #include <iostream> #include <cstring> using namespace std; ...

  2. vue单页(spa)前端git工程拆分实践

    背景 随着项目的成长,单页spa逐渐包含了许多业务线 商城系统 售后系统 会员系统 ... 当项目页面超过一定数量(150+)之后,会产生一系列的问题 可扩展性 项目编译的时间(启动server,修改 ...

  3. 关于一个function abc() 内 return一个值, 或者多个值写法

    1.想return一个值,选第一种写法 function abc(){ a = '我是adad' return a } console.log(abc) // ==> 这个是错的,不要这样写,经 ...

  4. App原生、混合、纯WEB开发模式的优劣分析

    什么叫做原生App? 什么是混合app? 什么是Web App开发? Native App开发即我们所称的传统APP开发模式(原生APP开发模式),该开发针对IOS.Android等不同的手机操作系统 ...

  5. MATLAB中的函数句柄及其应用

    1.函数句柄的创建 函数句柄(function handle)是MATLAB中的一类特殊的数据结构,它的地位类似于其它计算机语言里的函数对象(Javascript,Python),函数指针(C++), ...

  6. 制作jar包到本地仓库

    转载地址:http://blog.csdn.net/yzllz001/article/details/54529606 项目要用到支付宝的扫码支付,后台使用的maven 问了客服 官方目前没有 mav ...

  7. Centos6 修复grub损坏故障

    1.查看系统中的/boot/grub/grub.conf # grub.conf generated by anaconda # # Note that you do not have to reru ...

  8. jenkins项目名称改后,同步nginx配置

    jenkins项目名称修改后,workspace的名称会同步更改,构建完了和原来的不是一个路径,如果每个前端项目一个单独的tomcat的话,需要更改nginx配置 /etc/nginx/conf.d

  9. (转) Java中的负数及基本类型的转型详解

    (转) https://my.oschina.net/joymufeng/blog/139952 面这行代码的输出是什么? 下面两行代码的输出相同吗? 请尝试在Eclipse中运行上面的两个代码片段, ...

  10. [HAOI2010]软件安装(Tarjan,树形dp)

    [HAOI2010]软件安装 题目描述 现在我们的手头有N个软件,对于一个软件i,它要占用Wi的磁盘空间,它的价值为Vi.我们希望从中选择一些软件安装到一台磁盘容量为M计算机上,使得这些软件的价值尽可 ...