Java并发编程的艺术笔记(二)——wait/notify机制
一.概述
一个线程修改了一个对象的值,另一个线程感知到变化从而做出相应的操作。前者是生产者,后者是消费者。
等待/通知机制,是指一个线程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机制的更多相关文章
- Java并发编程实战 读书笔记(二)
关于发布和逸出 并发编程实践中,this引用逃逸("this"escape)是指对象还没有构造完成,它的this引用就被发布出去了.这是危及到线程安全的,因为其他线程有可能通过这个 ...
- 多线程的通信和同步(Java并发编程的艺术--笔记)
1. 线程间的通信机制 线程之间通信机制有两种: 共享内存.消息传递. 2. Java并发 Java的并发采用的是共享内存模型,Java线程之间的通信总是隐式执行,通信的过程对于程序员来说是完全透 ...
- Java并发编程的艺术笔记(七)——CountDownLatch、CyclicBarrier详解
一.等待多线程完成的CountDownLatch CountDownLatch允许一个或多个线程等待其他线程完成操作,像加强版的join.(t.join()是等待t线程完成) 例: (1)开启多个线程 ...
- Java并发编程的艺术笔记(五)——Java中的锁
一.Lock接口的几个功能: 显示的获取和释放锁 尝试非阻塞的获取锁 能被中断的获取锁 超时获取锁 使用方式: Lock lock = new ReentrantLock(); lock.lock() ...
- Java并发编程的艺术笔记(九)——FutureTask详解
FutureTask是一种可以取消的异步的计算任务.它的计算是通过Callable实现的,多用于耗时的计算. 一.FutureTask的三种状态 二.get()和cancel()执行示意 三.使用 一 ...
- Java并发编程的艺术(十二)——并发容器和框架
ConcurrentHashMap 为什么需要ConcurrentHashMap HashMap线程不安全,因为HashMap的Entry是以链表的形式存储的,如果多线程操作可能会形成环,那样就会死循 ...
- Java并发编程的艺术· 笔记(1)
目录 1.volatile的原理 2.Synchonized 3.无锁-偏向锁-轻量级锁-重量级锁 4.Java实现原子操作 1.volatile的原理 如何保持可见性: 1)将当前处理器缓存行的数据 ...
- Java并发编程的艺术(十二)——线程安全
1. 什么是『线程安全』? 如果一个对象构造完成后,调用者无需额外的操作,就可以在多线程环境下随意地使用,并且不发生错误,那么这个对象就是线程安全的. 2. 线程安全的几种程度 线程安全性的前提:对『 ...
- Java并发编程的艺术笔记(八)——线程池
一.线程池的主要处理流程 ThreadPoolExecutor执行execute方法分下面4种情况. 1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步需要获 ...
随机推荐
- 解决IOS把数字渲染为电话号码,颜色为蓝色解决方案
可将telephone=no,则手机号码不被显示为拨号链接<meta name="format-detection" content="telephone=no&q ...
- 安装运行zookeeper的坑
从官网下载zookeeper的地址中有俩文件 一个是apache-zookeeper-3.5.5.tar.gz ,另一个是apache-zookeeper-3.5.5-bin.tar.gz 若是使用前 ...
- 解决sudo用户找不到环境变量的问题
出于安全方面的考虑,使用sudo执行命令将在一个最小化的环境中执行,环境变量都重置成默认状态.所以PATH这个变量不包括用户自定义设置的内容 在sudo用户的主目录里的.bashrc中添加如下内容即可 ...
- vue-cli解决兼容ie的es6+api问题
官网:https://cli.vuejs.org/zh/guide/browser-compatibility.html#usebuiltins-usage https://github.com/vu ...
- mac StarUML3.0.2破解
mac StarUML3.0.2破解 StarUML是一款开放源码的UML开发工具,是由韩国公司主导开发出来的产品,可以直接到StarUML网站下载.可以用来创建UML类图. 破解办法是通过asar工 ...
- oracle使用between and边界问题
如果是只查询某两个月之间的数据,会默认从每个月的1号开始查,包前包后 eg:select * from test_hsj where regdate between to_date('2015-05' ...
- lnmp 环境搭建后,pathinfo 模式支持的配制。
ThinkPHP的四种URL模式:0(普通模式);1(PATHINFO模式);2(REWRITE模式);3(兼容模式) nginx需要PATHINFO模式,但需要更改nginx配置文件让其支持PATH ...
- [DllImport("kernel32.dll")]使用
C#中读取ini配置文件 [DllImport("kernel32.dll")] using System;using System.Collections.Generic;usi ...
- 第三篇:解析库之re、beautifulsoup、pyquery
BeatifulSoup模块 一.介绍 Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库.它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式.Be ...
- 五大Linux简单命令解决系统性能问题
五大Linux简单命令解决系统性能问题 2010-12-17 10:07 James Turnbull TechTarget中国 字号:T | T 管理Linux主机的性能看起来经常象是在变魔术一样. ...