java EE初阶 — wait 和 notify

news2024/11/16 7:42:09

文章目录

  • 1.wait 和 notify
    • 1.1 wait()方法
    • 1.2 notify()方法
    • 1.3 notifyAll()方法

1.wait 和 notify

线程最大的问题是抢占式指向,随机调度。而写代码的时候,确定的东西会比较好。

于是就有程序猿发明了一些办法,来控制线程之间的执行顺序。
虽然线程在内核里的调度是随机的,但是可以通过一些 API 然线程主动阻塞,主动放弃 CPU。(给别的线程让路)

比如,t1 t2 两个线程,希望 t1 先干活,干的差不多的时候,再让 t2 来干。
就可以让 t2 wait 。(阻塞,主动放弃 CPU)

上面的场景,使用 joinsleep 无法做到。
使用 jion 则必须要 t1 彻底执行完毕,t2 才可以运行。
如果是希望 t1 先干50%的活,就让 t2 开始行动,join 无能为力。
使用 sleep 指定一个休眠时间,但是 t1 执行的这些活,到底花了多少的时间,不好估计。

于是就可以使用 waitnotify

waitnotifynotifyAll 这几个类,都是,Object类中的方法。

1.1 wait()方法

wait 的作用是进行阻塞。

当某个线程调用 wait 方法时,就会进入阻塞(无论是通过哪个对象 wait 的)。
此时的状态就处在 WAITING


先来看一段代码。

package thread;

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

throws InterruptedException
有很多带阻塞功能的方法都有这个异常。
这些方法都是可以被 interrupt 方法通过这个异常唤醒。

wait 不加任何参数就会一直等待,直到有其他的线程唤醒它。

上面的代码会抛一个异常。


这是一个非法的锁状态异常

锁的状态:被加锁的状态和解锁的状态。

wait 的操作分为:

  1. 先释放锁。
  2. 进行阻塞等待
  3. 收到通知之后,重新尝试获取锁,并且在获取锁之后继续往下执行。

这里的锁的状态异常,就是没加锁,就要要释放锁了。

解决办法就是搭配 synchronized 来使用。

package thread;

public class ThreadDemo18 {
    public static void main(String[] args) throws InterruptedException{
        Object object = new Object();
        synchronized (object) {
            System.out.println("wait之前");
            object.wait();
            System.out.println("wait之后");
        }

    }
}



虽然这里的 wait 是阻塞在 synchronized 代码块里了,
但是实际上,这里的阻塞是释放了锁的。
此时其他线程是可以获取到 object 这个对象的锁的。

此时这里的阻塞就处于 WAITING

1.2 notify()方法

notify 方法是唤醒等待的线程。

来看一段代码

package thread;

public class ThreadDemo19 {
    public static void main(String[] args) {
        Object object = new Object();
        Thread t1 = new Thread(() -> {
            //线程1负责进行等待
            System.out.println("t1 wait之前");
            try {
                synchronized (object) {
                    object.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1 wait之后");
        });

        Thread t2 = new Thread(() -> {
            //线程2负责唤醒
            System.out.println("t2 notify之前");
                synchronized (object) {
                    object.notify();
                }
            System.out.println("t2 notify之后");
        });
        t1.start();
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t2.start();
    }
}


此处,先执行了 wait 很明显 操作阻塞了,没有看到 wait 之后的打印。
接下来执行到了 t2 ,t2 进行了 notify 的时候,才会把 t1 的 wait 唤醒,t1 才能继续执行。

此处的通知和 wait 配对。
如果 wait 使用的对象和 notify 使用的对象不同。
此时的 notify 不会有任何效果。(notify 只能唤醒在同一个对象上等待的线程)

如果的代码这里写作:t1.startt2.start
由于线程不确定性,此时不能保证是先执行 wait,后执行 notify

如果调用 notify 此时没有 wait 。此处的 wait 是无法被唤醒的。
因此此处的代码要求保证先执行 wait 后执行 notify,这样才是有意的。


wait 和 sleep 的区别
wait 的带有时间的版本看起来就和 sleep 有点像。
但是其实还是有区别的,虽然都是可以指定等待的时间,也都能被提前唤醒,
(wait 是被 nottify 唤醒,sleep 是被 interrupt 唤醒)
但是这里表示的含义截然不同。

  • notify 唤醒 wait 是不会有任何异常的。(正常的业务逻辑)
  • interr 唤醒 sleep 则是会出现异常。(表示一个出问题的逻辑)


下面看一道练习:

有三个线程,输出字母 ABC,控制三个线程分别按照 ABC 的顺序打印出来。

package thread;

public class ThreadDemo20 {
    //有三个线程,控制三个线程分别按照 ABC 的顺序打印出来
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println('A');
        });

        Thread t2 = new Thread(() -> {
            System.out.println('B');
        });

        Thread t3 = new Thread(() -> {
            System.out.println('C');
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

因为它的结果是随机调度的因此不能确定先输出哪个,结果可能是 ABC,也可能是ACB,或者是其他的结果。



我们需要通过 wait 和 notify 告知 线程1 在打印完A之后通知线程2可以打印B了
要保证线程2要阻塞到A打印结束,才会开始打印B。
线程3也是同理,直到按照顺序打印结束是会出现 ABC。


下面是优化过的版本。

package thread;

public class ThreadDemo20 {
    //有三个线程,控制三个线程分别按照 ABC 的顺序打印出来
    public static void main(String[] args) throws InterruptedException{
        Object locker1 = new Object();
        Object locker2 = new Object();

        Thread t1 = new Thread(() -> {
            System.out.println('A');
            synchronized (locker1) {
                //告知线程2B可以打印了
                locker1.notify();
            }
        });

        Thread t2 = new Thread(() -> {
            //等待线程1打印完A再打印B
            synchronized (locker1) {
                try {
                    locker1.wait();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println('B');
            synchronized (locker2) {
                //告知线程3C可以打印了
                locker2.notify();
            }
        });

        Thread t3 = new Thread(() -> {
            synchronized (locker2) {
                try {
                    locker2.wait();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println('C');
        });

        t1.start();
        t2.start();
        t3.start();
    }
}

通过 wait 和 notify 来实现三个线程之间相互等待和通知的作用。


c此时就是预期好的顺序了。

但是这样还是会有一些小问题:

如果是先执行线程2的 notify 后执行线程2的 wait ,此时程序就会僵住

解决办法就是 保证线程1的启动速度要慢于线程2和线程3

关键代码如下:

 t2.start();
 t3.start();
 Thread.sleep(1000);
 t1.start();

调整完线程1的顺序后,再加一个睡眠时间 1000 ms。

1.3 notifyAll()方法

notify 只能唤醒某一个等待线程,而 notifyAll 可以唤醒所有的等待线程。


注意:

虽然是同时唤醒多个线程, 但是这多个线程都需要竞争锁. 所以并不是同时执行, 而仍然是有先有后的执行。


这是 notify 只能唤醒一个。


这是 notifyAll 可以换线多个线程,但是多个线程需要竞争一把锁。

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

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

相关文章

火山引擎 RTC 助力抖音百万并发“云侃球”

动手点关注干货不迷路1. 背景及技术挑战从电视看直播到手机电脑看直播,直播技术的发展让观众可以随时、随地观看自己喜欢的比赛,并且在看比赛时通过发送表情、发文字进行互动。但表情、文字承载的信息量较小、沟通效率低,我们无法像线下一起看…

一大波节日来袭,App Store节日营销请注意!

11 月已经过去,在过去的 11 月里,我们经历了万圣节、双 11、世界杯、感恩节、黑色星期五等非常重要的营销节点。 在新的 12 月,我们将迎来世界杯闭幕、双12、平安夜、圣诞节等重要营销机遇。在未来,我们还会迎来新春营销的重要机…

图形API学习工程(29):解决在shader文件中使用include的问题

工程GIT地址:https://gitee.com/yaksue/yaksue-graphics 无用的前言 看了下提交记录,这个工程上次更新已经是一年以前了。最近想想,还是应该回来再继续学学,暂且不论是否对工作有帮助,我在这个工程上获得的愉悦感相比…

Excel 是您最容易被忽视的设计工具 设计师对世界排名第一的电子表格工具的看法——如何构建信息图表、仪表板、演示文稿等

人们对 Excel 有很多误解。许多人认为它不过是处理临时预算的电子表格工具。或者它非常适合处理数据,但您需要像 PowerPoint 这样的单独工具才能很好地显示它。 这些误解限制了我们使用 Excel 的方式。 但 Excel 的功能远不止于此,它所需要的只是了解一些鲜为人知的功能。我…

JAVA SCRIPT设计模式--创建型设计模式之工厂方法(3)

JAVA SCRIPT设计模式是本人根据GOF的设计模式写的博客记录。使用JAVA SCRIPT语言来实现主体功能,所以不可能像C,JAVA等面向对象语言一样严谨,大部分程序都附上了JAVA SCRIPT代码,代码只是实现了设计模式的主体功能,不代…

机器学习笔记之配分函数(三)对比散度

机器学习笔记之配分函数——对比散度引言回顾:随机最大似然求解模型参数的过程随机最大似然的缺陷吉布斯采样的缺陷与对比散度思想对比散度名称的由来从KL\mathcal K\mathcal LKL散度的角度描述极大似然估计对比散度的本质引言 上一节介绍了随机最大似然(Stochasti…

第十四届蓝桥杯集训——JavaC组第四篇——ASCII码表与Scanner扫描器

第十四届蓝桥杯集训——JavaC组第四篇——ASCII码表与Scanner扫描器 目录 第十四届蓝桥杯集训——JavaC组第四篇——ASCII码表与Scanner扫描器 ASCII码表 Scanner扫描器 1、Scanner含义 2、使用方法: next和nextLine的区别 next: nextLine&#…

微服务框架 SpringCloud微服务架构 23 搜索结果处理 23.2 分页

微服务框架 【SpringCloudRabbitMQDockerRedis搜索分布式,系统详解springcloud微服务技术栈课程|黑马程序员Java微服务】 SpringCloud微服务架构 文章目录微服务框架SpringCloud微服务架构23 搜索结果处理23.2 分页23.2.1 分页23.2.2 深度分页问题23.2.3 深度分页…

Zookeeper常用命令大全之四字监控命令

文章目录四字监控命令0. 官方文档1. conf命令2. cons命令3. crst命令4. dump命令5. envi命令6. ruok命令7. stat命令8. srst命令9. wchs命令10. wchc命令(一般不用)11. wchp命令(一般不用)12. mntr命令四字监控命令 zookeeper支持某些特定的四字命令与其的交互。它们大多是查询…

自媒体短视频,如何起一个让人一看就记住的昵称?看一眼就知道你的定位

上一篇我们说了头像如何设置更吸引人,今天上午在我赢且力手小禾呈序里聊了一个好听好记的昵称应该是怎样的? 这是文字版,大家可以看一看。 昵称起名的方式无非几种: 职业加昵称,昵称加地点,昵称加领域&a…

全球数据集大全之mavenanalytics学习分析世界杯数据集

mavenanalytics数据集平台介绍 探索和下载由 Maven 讲师精心挑选的示例数据集。练习将您的数据分析和可视化技能应用于现实世界的数据,从航班延误和电影收视率到鲨鱼袭击和不明飞行物目击事件。 平台效果 是否支持下载 可以免费下载 https://www.mavenanalytics.io/data-p…

Java基于jsp大学生收支管理系统

随着计算机技术的飞速发展,计算机在系统管理中的应用越来越普及,利用计算机实现各个系统的管理显得越来越重要。随着人们消费水平的提高,消费量一直在加大 ,可是如何能够合理的管理和记录自己的消费方向是一个很大的问题&#xff…

深度学习炼丹-不平衡样本的处理

前言一,数据层面处理方法 1.1,数据扩充1.2,数据(重)采样 数据采样方法总结 1.3,类别平衡采样 二,算法(损失函数)层面处理方法 2.1,Focal Loss2.2,…

Akka 学习(三)Actor的基本使用

目录一 基本案例1.1 Java 版1.2 Scala版二 Actor的创建2.1 ActorRef2.2 Props2.3 ActorSelection三 Promise、Future和事件驱动的编程模型3.1 阻塞与事件驱动3.2 Future进行Actor响应3.2.1 Java版3.2.2 Scala 版3.2.3 总结3.3 成功处理3.4 失败处理3.5 恢复3.6 链式调用3.7 结果…

小程序开发工具怎么使用?

小程序开发工具怎么用? 小程序开发工具分两种: 一种是微信官方提供的微信开发者工具 这个需要从事代码行业,职业是程序员又或者对代码知识有一定程度的人,才能上手使用。 另一种是第三方小程序开发平台,提供的小程序开发工具 …

python环境、基础语法、几种常见的数据类型

文章目录前言一、基本知识介绍二、举例实操以及重要知识再现(列表、元组、集合、字典)前言 一、基本知识介绍 python基础 标准库与扩展库中的对象的导入与使用: import 模块名(as别名) import numpy as np from 模块名 import 对象名&#x…

程序人生:快来一起学习软件测试,一起月薪过万(测试理论基础学习)

测试基础 为什么要有测试呢?现在软件已经和人的生活息息相关了,所以保证软件的稳定很重要。但是所有开发出来的软件都是有缺陷的。包括代码错误,逻辑错误,设计不合理等。 测试的目的 测试的目的主要有四个点 1找到软件缺陷 2…

Flink SQL增量查询Hudi表

前言 前面总结了Spark SQL增量查询Hudi表和Hive增量查询Hudi表。最近项目上也有Flink SQL增量查询Hudi表的需求,正好学习总结一下。 官网文档 地址:https://hudi.apache.org/cn/docs/querying_data#incremental-query 参数 read.start-commit 增量查…

WWW2022 | 基于领域增强的图对比协同过滤方法+代码实践

嘿,记得给“机器学习与推荐算法”添加星标今天跟大家分享一篇将对比学习应用于图协同过滤方法的文章,该论文发表于WWW2022会议上。其主要思想是在图神经网络协同过滤方法上应用了两种领域类型的对比学习方法,分别是显式的结构领域和隐式的语义…

TGK-Planner-前后端路径规划(基于梯度的后端无约束优化)

高速移动无人机的在线路径规划一直是学界当前研究的难点,引起了大量机器人行业的研究人员与工程师的关注。然而无人机的计算资源有限,要在短时间内规划出一条安全可执行的路径,这就要求无人机的运动规划算法必须轻型而有效。本文将介绍一种无…