Java 多线程 —— wait 与 notify

引言

认识一下 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的使用场景,而且不受锁的限制,书写简便。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558947.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

重写equal()时为什么也得重写hashCode()之深度解读equal方法与hashCode方法渊源

重写equal()时为什么也得重写hashCode()之深度解读以及equal方法与hashCode方法渊源转载自&#xff1a;http://blog.csdn.net/javazejian/article/details/51348320 今天这篇文章我们打算来深度解读一下equal方法以及其关联方法hashCode()&#xff0c;我们准备从以下几点入手分…

Java8————Optional

引言 Optional 类是一个可以为null的容器对象。如果值存在则isPresent()方法会返回true&#xff0c;调用get()方法会返回该对象。 Optional 是个容器&#xff1a;它可以保存类型T的值&#xff0c;或者仅仅保存null。Optional提供很多有用的方法&#xff0c;这样我们就不用显式进…

问题反馈信息处理平台开发过程

问题反馈信息处理平台开发过程 “问题反馈信息处理平台”是一个将用户反馈上来的出错信息进行处理和收集的一个平台。 这个项目主要都是在实习的时候由我一个人进行开发&#xff0c;我导师在旁边进行指导完成的。 该项目的技术架构主要是&#xff1a; 前端主要基于Vue框架的…

Java8————Base64

Base64&#xff1f; Base64是一种用64个字符来表示任意二进制数据的方式。 对于二进制文件如图片、exe、音频、视频等&#xff0c;包含很多无法显示和打印的字符&#xff0c;如果希望能够通过记事本这样的文本处理软件处理二进制数据&#xff0c;就需要一个二进制转字符串的转…

大众点评后端项目解析

restful Api是一种设计风格&#xff1a; 启动前端项目&#xff1a;在前端项目根目录 npm install&#xff1a;加载依赖包 npm run mock&#xff1a;提供模拟数据的接口&#xff0c;前端脱离于后台&#xff1b;start /b npm run mock&#xff08;于后台运行&#xff09; npm…

Java中类及方法的加载顺序

代码展示 请运行下面代码&#xff0c;查看运行结果&#xff0c;并带着问题&#xff0c;尝试第二次debug程序。 class A {private static int numA;private int numA2;static {System.out.println("A的静态字段 : " numA);System.out.println("A的静态代码块…

新手入门教程-------Spring Boot中集成RabbitMQ

AMQP&#xff1a;是Advanced Message Queuing Protocol的简称&#xff0c;高级消息队列协议&#xff0c;是一个面向消息中间件的开放式标准应用层协议。 定义了以下特性&#xff1a; 消息方向消息队列消息路由&#xff08;包括&#xff1a;点到点和发布-订阅模式&#xff09;可…

Java 多线程 —— ReentrantLock 与 Condition

引言 ReentrantLock 是 JUC 下的一个功能强劲的锁工具&#xff0c;支持公平锁、非公平锁&#xff0c;以及多等待队列的 Condition 。 也常常被称为“手动锁”。本篇博客主要分析它的使用方法以及 Condition 实现的一个生产者消费者模式。 一、可替代 synchronized 的手动锁 …

Rabbitmq+Springboot设计秒杀应用

秒杀业务的核心是库存处理&#xff0c;用户购买成功后会进行减库存操作&#xff0c;并记录购买明细。当秒杀开始时&#xff0c;大量用户同时发起请求&#xff0c;这是一个并行操作&#xff0c;多条更新库存数量的SQL语句会同时竞争秒杀商品所处数据库表里的那行数据&#xff0c…

Java8————Stream API

引言 Java8 加入了java.util.stream包&#xff0c;这个包中的相关API将极大的增强容器对象对元素的操作能力。 它专注于对集合对象进行各种便利、高效的聚合操作&#xff0c;或大批量数据处理。 Stream API借助于同样新出现的Lambda表达式&#xff0c;极大的提高了编程效率和…

MySQL数据库知识点总结

数据库&#xff1a; 数据库索引的好处&#xff1a;索引是对数据库表中的一个或多个列的值进行排序的结构&#xff0c;这样检索或者查询某条记录的时候就不在是顺序查找&#xff0c;而是使用特定的查找方式进行查找&#xff0c;如通过二分查找或者是hash值来查找&#xff0c;提高…

Java8 函数式对齐约定————Eclipse自定义代码风格

引言 Java8 的函数式代码风格在Stream的使用上尤为突出。尽管我们可以通过连续调用函数完成一系列操作&#xff0c;但是其可读性并不能保证&#xff0c;还需要有与之相辅的Code Style。例如&#xff0c;请尝试阅读下面两段完全相同的代码&#xff1a; 未遵守约定格式&#xf…

Java核心篇之JVM--day3

Java核心篇之JVM--day3 Java JVM详解--通俗易懂教程 JVM&#xff1a;Java虚拟机的简称。 谈到JVM&#xff0c;通常会聊到三个问题&#xff1a; 1. 什么时候触发Java GC&#xff1f; 2. 对什么东西进行Java GC&#xff1f; 3. 如何进行Java GC&#xff1f; 首先解决第…

使用springboot来实现WebLog

使用websocket技术实时输出系统日志到浏览器端&#xff0c;实现WebLog boot-websocket-log&#xff1a; spring boot系统中使用websocket技术实时输出系统日志到浏览器端&#xff0c;因为是实时输出&#xff0c;所有第一时间就想到了使用webSocket,而且在spring boot中&#…

设计模式---观察者模式介绍与理解

设计模式---观察者模式介绍与理解&#xff1a; 观察者模式原理&#xff1a;类似于定牛奶业务 1. 奶站&#xff0c;subject&#xff1a;登记注册&#xff0c;移除&#xff0c;通知&#xff08;register&#xff0c;remove&#xff0c;notify&#xff09; 2. 用户&#xff0c;…

CRS-4995: The command ‘start resource’ is invalid in crsctl.

ntp时间调整后&#xff0c;节点1&#xff0c;advm 和acfs offline 处理办法&#xff1a; /u01/app/12.2.0.1/grid/bin/crsctl stop crs /u01/app/12.2.0.1/grid/bin/crsctl start crs 曾经尝试如下命令不起作用 /u01/app/12.2.0.1/grid/bin/acfsload start /u01/app/12.2…

抽象工厂模式升级版————泛型化实现

引言 今天回看之前总结的抽象工厂模式的实现《Java常用设计模式————抽象工厂模式》&#xff0c;聚焦于抽象工厂模式的缺点&#xff0c;试着改进了一下。 回顾一下抽象工厂模式的缺点&#xff1a; 在添加新的产品类型时&#xff0c;难以扩展抽象工厂来生产新种类的产品。…

发生在“注解”@的那些事儿

注解&#xff1a; 自定义注解&#xff1a; 注解和类&#xff0c;接口一样&#xff0c;属于一种数据类型 注解可以放在类&#xff0c;方法&#xff0c;属性上面 注解可以有属性&#xff0c;也可以没有属性 注解有作用范围 &#xff08; 源码期间&#xff08;String&#…

Java常用设计模式————建造者模式

引言 建造者模式&#xff08;Builder Pattern&#xff09;使用多个简单对象一步一步构建成一个复杂的对象。这种类型的设计模式属于建造型模式&#xff0c;它提供了一种创建对象的最佳方式。 一个Builder会一步步构建最终的对象。该Builder类是独立于其他对象的。 实现概要 …

使用动态代理解决网站字符集编码问题:(之前通过拦截器)

使用动态代理解决网站字符集编码问题&#xff1a;&#xff08;之前通过拦截器&#xff09; 设计模式&#xff1a; 在软件开发的过程中&#xff0c;遇到相识的问题&#xff0c;将问题的解决方式抽象为模型&#xff08;套路&#xff09; 单例模式&#xff08;静态代码只会执行一…