引言

认识一下 Object 类中的两个和多线程有关的方法:wait 和 notify。

wait,当前线程进入 WAITING 状态,释放锁资源

notify,唤醒等待中的线程,不释放锁资源

一、使用 wait-notify 实现一个监控程序

实现一个容器报警功能,两个线程分别执行以下任务:

t1 为容器添加10个元素;

t2实时监控容器中元素的个数,当个数为5时,线程2给出提示并结束。

1.1 简单的 while-true 版本

/*** 一个普通的容器*/
class Container {private volatile List<Object> values = new ArrayList<>();public void add(Object value) {values.add(value);}public Integer sise() {return values.size();}
}
public class T_Alert01 {public static void main(String[] args) {Container container = new Container();new Thread(() -> {while (true) {if (container.sise() == 5) {System.out.println("alert 5!");break;}}}, "T2").start();new Thread(() -> {for (int i = 0; i < 10; i++) {container.add(new Object());System.out.println("已添加:" + container.sise());}}, "T1").start();}
}

程序解析:监控线程 T2 通过 while-true 监控容器内的元素数量,但当 size == 5 时,还未来得及报警,T1 就又添加了多个元素;而且 while-true 会浪费很多CPU 资源。

显然,这么做无法满足我们的要求。

1.2 wait-notify 初版

public class T_Alert02 {public static void main(String[] args) throws InterruptedException {Container container = new Container();final Object lock = new Object();new Thread(() -> {synchronized (lock) {if (container.sise() != 5) {try {lock.wait();} catch (InterruptedException e) {}}System.out.println("alert 5!");}}, "T2").start();TimeUnit.SECONDS.sleep(1);new Thread(() -> {synchronized (lock) {for (int i = 0; i < 10; i++) {container.add(new Object());System.out.println("已添加:" + container.sise());if (container.sise() == 5) {lock.notify();}}}}, "T1").start();}
}

程序解析:T2监控线程先去判断容器大小,如果未达到报警标准,则等待在 lock 对象上,WAITING 是一种挂起状态,不消耗CPU资源。

T1 线程在达到 5 个时,触发一个 notify方法,唤醒其他等待中的线程。然而,仅仅是 notify 还不足以做到实时唤醒 T2 报警,上述代码无论执行多少次都是最后输出报警信息,想想这是为什么?

1.3 wait-notify 完整版

public class T_Alert03 {public static void main(String[] args) throws InterruptedException {Container container = new Container();final Object lock = new Object();new Thread(() -> {synchronized (lock) {if (container.sise() != 5) {try {lock.wait();} catch (InterruptedException e) {}}System.out.println("alert 5!");lock.notify();}}, "T2").start();TimeUnit.SECONDS.sleep(1);new Thread(() -> {synchronized (lock) {for (int i = 0; i < 10; i++) {container.add(new Object());System.out.println("已添加:" + container.sise());if (container.sise() == 5) {lock.notify();try {lock.wait();} catch (InterruptedException e) {}}}}}, "T1").start();}
}

程序解析:该版本的 wait-notify 可以满足实际题目要求,达到实时触发警报,在 T1 notify 之后立刻调用 wait 进入状态;另一边T2在被唤醒并输出报警信息后,也需要再次调用 notify 唤醒 其他等待线程继续执行任务。

二、notify 和 notifyAll

如果有多个线程等待同一个对象锁,那么 notify 方法会随机唤醒一个线程,它无法做到精准唤醒。notifyAll 是唤醒全部等待线程,但需要明确是是,由于wait-notify 的操作模式是基于锁对象的,所以即便是 notifyAll 也是非公平竞争锁资源,即哪个线程抢到锁就去执行同步代码。

三、wait-notify 的原理

我们声明了一个 Object 对象,直接调用 wait 方法会怎样呢?

public class T_Wait_Notify {public static void main(String[] args) throws InterruptedException {final Object lock = new Object();lock.wait();}
}

监视器状态异常。这是因为 wait-notify 必须基于“锁对象”,而这个锁对象可不是普通的一个什么对象都可以。在了解了 synchronized 关键字的实现原理后,我们知道,JVM 为每个对象都关联了一个 monitor 对象,进入同步代码块和结束同步代码块就对应着 monitor enter 和 monitor exit 两条指令,也就是说,如果不使用 synchronized,就不存在所谓的 wait 和 notify。所以正确的写法一定是:

synchronized (lock) {lock.wait();
}

在 wait 方法的 Java doc 中这样说明,wait 方法会令当前线程将自己放入锁对象的 wait set 中,并且放弃此对象上所有同步的同步声明。

当唤醒时,当前线程必须持有该对象的监视器,才能继续执行。

总之,wait 和 notify 是和 对象的 monitor 紧密相关的,而 monitor 又是 synchronized 重量级锁模式的实现原理,所以理解wait 和 notify的 同时 也需要深入理解 synchronized 关键字。

扩展:闭锁实现的监控报警

闭锁是一种同步工具类,可以延迟线程的进度直到其到达终止状态。

闭锁相当于一扇门:在闭锁到达结束状态之前,这扇门一直是关闭的,不允许任何线程通过,当到达结束状态时,这扇门会打开并允许所有的线程通过。

下面的程序是通过 CountDownLatch 闭锁来实现的一个监控容器的版本,虽然可以达到要求,但很遗憾,程序中必须通过 sleep 方法让线程跑的“没那么快”,否则,去掉 sleep 的话依然会出现 while-true 的执行结果,即警报没那么实时了:

public class T_Alert04CountDownLatch {public static void main(String[] args) {Container container = new Container();CountDownLatch latch = new CountDownLatch(1);new Thread(() -> {if (container.sise() != 5) {try {latch.await();} catch (InterruptedException e) {}}System.out.println("alert 5!");}, "T2").start();new Thread(() -> {for (int i = 0; i < 10; i++) {container.add(new Object());System.out.println("已添加" + container.sise());if (container.sise() == 5) {latch.countDown();}try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {}}}, "T1").start();}
}

总结

使用 wait-notify 切换线程状态是一种细粒度操作,开发者需要非常了解他们的执行逻辑以及线程的生命周期

wait 和 notify使用时必须将对象锁定,否则无法使用。

线程在使用对象的wait方法后会进入等待状态,notify() 和 notifyAll() 可以唤醒其他线程,注意 notify 是随机唤醒一个线程

wait-notify的操作是相对复杂的,虽然强大,但是在处理复杂的业务逻辑中书写较麻烦,相当于多线程中的汇编语言。

使用CountDownLatch可以有效的替代wait和notify的使用场景,而且不受锁的限制,书写简便。

Java 多线程 —— wait 与 notify相关推荐

  1. Java多线程wait()和notify()方法图解

    多线程wait()和notify()方法详解 文章目录 多线程wait()和notify()方法详解 前言 一.线程间等待与唤醒机制 二.等待方法wait() 三.唤醒方法notify() 四.关于w ...

  2. Java多线程中wait, notify and notifyAll的使用

    本文为翻译文章,原文地址:http://www.journaldev.com/1037/java-thread-wait-notify-and-notifyall-example 在Java的Obje ...

  3. Java 多线程编程之 notify notifyAll wait lock unlock 算法

    写了一个类来理解java 同步机制的算法.这个类并不适合实战,而仅仅是算法层面进行理解. package multithread; import java.util.ArrayList;import ...

  4. java多线程 wait和notify方法

    public class ProductTest {public static void main(String[] args) {Clerk clerk = new Clerk();Producte ...

  5. Java多线程中notifyAll()方法使用教程

    简介 本文将承接<Java多线程wait()和notify()系列方法使用教程>,结合代码实例,补充讲解下notifyAll()方法的作用以及使用时需要注意的地方. 一.notifyAll ...

  6. java 多线程 notifyall_java多线程之 wait(),notify(),notifyAll()

    这几天在写一个java多线程服务器的功能,用到这些基础,自叹基础知识还需巩固,先写上一下这些说明,供自己和大家参考 wait(),notify(),notifyAll()不属于Thread类,而是属于 ...

  7. Java多线程之线程间协作 notify与wait的使用

    (转载请注明出处:http://blog.csdn.net/buptgshengod) 1.背景 Java多线程操作运用很广,特别是在android程序方面.线程异步协作是多线程操作的难点也是关键,也 ...

  8. 【收藏】Java多线程/并发编程大合集

    (一).[Java并发编程]并发编程大合集-兰亭风雨    [Java并发编程]实现多线程的两种方法    [Java并发编程]线程的中断    [Java并发编程]正确挂起.恢复.终止线程    [ ...

  9. 40个Java多线程问题总结

    (转) 这篇文章作者写的真是不错 40个问题汇总 1.多线程有什么用? 一个可能在很多人看来很扯淡的一个问题:我会用多线程就好了,还管它有什么用?在我看来,这个回答更扯淡.所谓"知其然知其所 ...

最新文章

  1. linux文件属性文文件类型知识
  2. UA SIE545 优化理论基础4 对偶理论简介2 弱对偶与Duality Gap
  3. SDL及扩展库在ARM-Linux 完整移植
  4. mysql5.6 忘记root密码后,如何找回密码?
  5. LWIP的数据包管理
  6. 【数码管识别】4识别成5或7的问题
  7. Matter-JS friction 摩擦力
  8. Python爬虫:通过Selenium库学习如何爬取京东畅销排行榜书籍
  9. 批量下载哔哩哔哩视频的工具
  10. 苹果iPhone手机用iTunes更新IOS14.3系统失败怎么解决
  11. 计算机桌面底边出现库如何去掉,桌面图标有蓝底怎么去掉完美全解决方案
  12. vb6.0企业版win7_教你安装纯净版windows系统
  13. matplotlib之pyplot模块之饼图(pie():基础参数,返回值)
  14. Latex——属于符号
  15. CodeForces #379(734A|734B|734C|734D|734E|734F)|二分查找|模拟|树的半径|位运算
  16. 京东财报图解:年营收9516亿增28% 全渠道取得阶段性进展
  17. 线性代数 --- 线性相关与线性无关(个人学习笔记)
  18. moviepy剪切视频spleeter视频降噪-CPUGPU
  19. 《会计学》账户与复式记账笔记的思维导图
  20. 用 Python 写一个天天酷跑 | 内附源码

热门文章

  1. C# ArrayList 与 string、string[] 的转换
  2. java中_null和“”的区别详解
  3. 利用MFC按钮使能(或禁用)属性使按钮变正常色(或灰色)
  4. eclipse搭建maven开发环境
  5. 调整 Windows系统参数网址
  6. char数组拷贝wchar数组
  7. 计组学习笔记(一):浮点数的表示和运算
  8. sentry + vue实现错误日志监控
  9. 移动端实现文字轮播_移动端轮播图实现
  10. ajax拼接外部变量,在ajax调用中访问函数外部变量的问题