本文部分摘自《Java 并发编程的艺术》

volatile 和 synchronize 关键字

每个处于运行状态的线程,如果仅仅是孤立地运行,那么它产生的作用很小,如果多个线程能够相互配合完成工作,则将带来更大的价值

Java 支持多个线程同时访问一个对象或者对象的成员变量,使用 volatile 关键字可以保证被修饰变量的可见性,意味着任一线程对该变量的任何修改,其他线程都可以立即感知到

synchronize 关键字可以修饰方法或者同步块,它主要确保多个线程在同一时刻,只能有一个线程处于方法或者同步块中,它保证了线程对变量访问的可见性和排他性。synchronize 关键字的实现,本质是对一个对象的监视器(monitor)进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由 synchronize 所保护对象的监视器

任何一个对象都拥有自己的监视器,任意一个线程对 Object 的访问(Object 由 synchronize 保护)的访问,首先要获得 Object 的监视器。如果获取失败,线程进入同步队列,线程状态变为 BLOCKED。当访问 Object 的前驱(获得了锁的线程)释放了锁,则该释放操作将唤醒阻塞在同步队列中的线程,使其重新尝试获取监视器

等待 - 通知机制

一个线程修改了一个对象的值,另一个线程感知到变化,然后进行相应的操作,前者是生产者,后者是消费者,这种通信方式实现了解耦,更具伸缩性。在 Java 中为了实现类似的功能,我们可以让消费者线程不断地循环检查变量是否符合预期,条件满足则退出循环,从而完成消费者的工作

while(value != desire) {
Thread.sleep(1000);
}
doSomething();

睡眠一段时间的目的是防止过快的无效尝试,这种实现方式看似能满足需求,但存在两个问题:

  • 难以确保及时性

    如果睡眠时间太长,就难以及时发现条件已经变化

  • 难以降低开销

    如果降低睡眠时间,又会消耗更多的处理器资源

使用 Java 提供了内置的等待 - 通知机制能够很好地解决上述问题,等待 - 通知的相关方法是任意 Java 对象都具备的

方法名称 描述
notify() 通知一个在对象上等待的线程,使其从 wait() 方法返回,返回的前提是该线程获取到了对象的锁
notifyAll() 通知所有等待在该对象上的线程
wait() 调用该方法的线程进入 WAITING 状态,只有等待另外的线程通知或被中断才返回,调用此方法会释放对象的锁
wait(long) 超时等待一段时间,参数时间是毫秒
wait(long, int) 对于超时时间更细粒度的控制,可以达到纳秒

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

下述例子中,创建两个线程 WaitThread 和 NotifyThread,前者检查 flag 值是否为 false,如果符合要求,进行后续操作,否则在 lock 上等待,后者在睡眠一段时间后对 lock 进行通知

public class WaitNotify {

    static boolean flag = true;
static Object lock = new Object(); public static void main(String[] args) throws InterruptedException {
Thread waitThread = new Thread(new Wait(), "WaitThread");
waitThread.start();
TimeUnit.SECONDS.sleep(1);
Thread notifyThread = new Thread(new Notify(), "NotifyThread");
notifyThread.start();
} static class Wait implements Runnable { @Override
public void run() {
// 加锁,拥有 lock 的 Monitor
synchronized (lock) {
// 继续 wait,同时释放 lock 的锁
while (flag) {
try {
System.out.println(Thread.currentThread() + "flag is true. wait @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 完成工作
System.out.println(Thread.currentThread() + "flag is false. running @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
}
}
} static class Notify implements Runnable { @Override
public void run() {
// 加锁,拥有 lock 的 Monitor
synchronized (lock) {
// 获取 lock 的锁,然后进行通知,通知时不会释放 lock 的锁
// 直到当前线程释放 lock 后,WaitThread 才能从 wait 方法中返回
System.out.println(Thread.currentThread() + " hold lock. notify @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
lock.notifyAll();
flag = false;
SleepUtils.second(5);
}
// 再次加锁
synchronized (lock) {
System.out.println(Thread.currentThread() + " hold lock again. sleep @ "
+ new SimpleDateFormat("HH:mm:ss").format(new Date()));
SleepUtils.second(5);
}
}
}
}

运行结果如下

上述结果的第三行和第四行顺序可能会互换,下面简单描述一下代码的执行过程

  1. WaitThread 线程先启动,NotifyThread 线程后启动,由于中间有睡眠一秒的操作,所以 WaitThread 线程首先获得锁
  2. WaitThread 线程循环判断条件是否满足,不满足则调用执行 lock.wait() 方法,释放 lock 对象上的锁,进入 lock 对象的等待队列中,进入等待状态
  3. 由于 WaitThread 线程释放了锁,所以 NotifyThread 获得 lock 对象上的锁,执行 lock.notifyAll() 方法,但并不会立即释放锁,只是通知所有等待在 lock 上的线程可以参与竞争锁了(notify 也同理),并把 flag 设为 false,本段代码执行结束,NotifyThread 线程释放锁,此时 WaitThread 线程和 NotifyThread 线程共同竞争 lock 的锁
  4. 无论谁先拿到锁,WaitThread 线程和 NotifyThread 线程都能顺利完成任务

等待 - 通知机制的经典范式

从上节的内容中,我们可以提炼出等待 - 通知机制的经典范式,该范式分为两部分,分别针对等待方(消费方)和通知方(生产者)

等待方遵循如下原则:

  • 获取对象上的锁
  • 如果条件不满足,调用对象的 wait() 方法,被通知后仍要检查条件
  • 条件满足则执行对应的逻辑

伪代码如下:

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

通知方遵循如下原则:

  • 获取对象上的锁
  • 改变条件
  • 通知所有等待在对象上的线程

伪代码如下:

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

Java 线程间通信 —— 等待 / 通知机制的更多相关文章

  1. JMM之Java线程间通讯——等待通知机制及其经典范式

    在并发编程中,实际处理涉及两个关键问题:线程之间如何通信及线程之间如何同步(这里的线程是指并发执行的活动实体). 通信是指线程之间以何种机制来交换信息.在共享内存的并发模型里,线程之间共享程序的公共状 ...

  2. Java线程间通信-回调的实现方式

    Java线程间通信-回调的实现方式   Java线程间通信是非常复杂的问题的.线程间通信问题本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现交互.   比如举一个简单例子,有一个多线程的 ...

  3. java 多线程:线程通信-等待通知机制wait和notify方法;(同步代码块synchronized和while循环相互嵌套的差异);管道通信:PipedInputStream;PipedOutputStream;PipedWriter; PipedReader

    1.等待通知机制: 等待通知机制的原理和厨师与服务员的关系很相似: 1,厨师做完一道菜的时间不确定,所以厨师将菜品放到"菜品传递台"上的时间不确定 2,服务员什么时候可以取到菜,必 ...

  4. Java线程间通信之wait/notify

    Java中的wait/notify/notifyAll可用来实现线程间通信,是Object类的方法,这三个方法都是native方法,是平台相关的,常用来实现生产者/消费者模式.我们来看下相关定义: w ...

  5. java多线程系列(三)---等待通知机制

    等待通知机制 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我的理解 ...

  6. Java——线程间通信

    body, table{font-family: 微软雅黑; font-size: 10pt} table{border-collapse: collapse; border: solid gray; ...

  7. Java Concurrency - wait & notify, 等待通知机制

    生产者消费者问题是一个常见的多线程同步案例:一组生产者线程和一组消费者线程共享一个初始状态为空.大小为 N 的缓冲区.只有当缓冲区没满的时候,生产者才能把消息放入缓冲区,否则必须等待:只有缓冲区不空的 ...

  8. java线程间通信:一个小Demo完全搞懂

    版权声明:本文出自汪磊的博客,转载请务必注明出处. Java线程系列文章只是自己知识的总结梳理,都是最基础的玩意,已经掌握熟练的可以绕过. 一.从一个小Demo说起 上篇我们聊到了Java多线程的同步 ...

  9. 说说Java线程间通信

    序言 正文 [一] Java线程间如何通信? 线程间通信的目标是使线程间能够互相发送信号,包括如下几种方式: 1.通过共享对象通信 线程间发送信号的一个简单方式是在共享对象的变量里设置信号值:线程A在 ...

随机推荐

  1. Struts 2学习(二)

    文章目录 @[toc] #动态结果 配置时不知道执行后的结果是哪一个,运行时才知道哪个结果作为视图显示给用户. #全局结果 全局就结果可满足一个包中多个Action共享一个结果. 全局结果位于pack ...

  2. ubuntu虚拟机重启后进入initramfs的解决办法

    问题:windows下使用VMware或者自己安装的ubuntu系统出现,不能正常进入系统,而是进入一个以initramfs开头的命令行界面! 原因:不正常的关闭系统,导致系统文件损坏,/dev/sd ...

  3. 36.Samba 文件共享服务1--安装及配置参数解释

    1.Samba 服务程序现在已经成为在Linux 系统与Windows系统之间共享文件的最佳选择. 1)安装: [root@localhost ~]#yum install samba Loaded ...

  4. java封装详解

    三大特性之---封装 封装从字面上来理解就是包装的意思,专业点就是信息隐藏,是指利用抽象数据类型将数据和基于数据的操作封装在一起,使其构成一个不可分割的独立实体,数据被保护在抽象数据类型的内部,尽可能 ...

  5. 详解MySQL事务原理

    老刘是即将找工作的研究生,自学大数据开发,一路走来,感慨颇深,网上大数据的资料良莠不齐,于是想写一份详细的大数据开发指南.这份指南把大数据的[基础知识][框架分析][源码理解]都用自己的话描述出来,让 ...

  6. docker部署 springboot 多模块项目+vue

    之前学习了docker,今天就来试试将这个项目打包成docker镜像并通过运行一个镜像来运行项目.这里使用的项目是el-admin.是一个开源的springboot后端管理框架(前端vue),有兴趣的 ...

  7. 牛客练习赛53 E-老瞎眼pk小鲜肉(思维+线段树+离线)

    前言 听说是线段树离线查询?? 做题做着做着慢慢对离线操作有点感觉了,不过也还没参透,等再做些题目再来讨论离线.在线操作. 这题赛后看代码发现有人用的树状数组,$tql$.当然能用树状数组写的线段树也 ...

  8. 2019牛客暑期多校训练营(第九场)D-Knapsack Cryptosystem(思维+子集和)

    >传送门<题意:给你一个有n个元素的数组,一个sum,让你找到数组的子集使得子集元素和等于sum,保证只有一个解决方案. (其中1≤n≤36,0≤ sum<9*1018,0<a ...

  9. P1903 [国家集训队]数颜色 / 维护队列 带修改莫队

    题目描述 墨墨购买了一套N支彩色画笔(其中有些颜色可能相同),摆成一排,你需要回答墨墨的提问.墨墨会向你发布如下指令: 1. Q L R代表询问你从第L支画笔到第R支画笔中共有几种不同颜色的画笔. 2 ...

  10. 【noi 2.6_9275】&【bzoj 3398】Bullcow(DP){Usaco2009 Feb}

    题意:一共有N只牡牛(公牛)和牝牛(母牛),每2只牡牛间至少要有K只牝牛才不会斗殴.问无斗殴发生的方案数. 解法:f[i][j]表示一共i只牛,最后一只是j(0为牝牛,1为牡牛)的方案数.f[i][0 ...