多线程wait()和notify()方法详解

news2024/12/28 8:05:49

多线程wait()和notify()方法详解

文章目录

  • 多线程wait()和notify()方法详解
  • 前言
  • 一、线程间等待与唤醒机制
  • 二、等待方法wait()
  • 三、唤醒方法notify()
  • 四、关于wait和notify内部等待问题(重要)
  • 五、完整代码(仅供测试用)
  • 六、wait和sleep方法的区别(面试题):
  • 总结


前言

博主个人社区:开发与算法学习社区

博主个人主页:Killing Vibe的博客

欢迎大家加入,一起交流学习~~


一、线程间等待与唤醒机制

wait()和notify()是Object类的方法,用于线程的等待与唤醒,必须搭配synchronized 锁来使用。

多线程并发的场景下,有时需要某些线程先执行,这些线程执行结束后其他线程再继续执行。

比如: 一个长跑比赛,裁判员要等跑步运动员冲线了才能宣判比赛结束,那裁判员线程就得等待所有的运动员线程运行结束后,再唤醒这个裁判线程。

二、等待方法wait()

wait 做的事情:

  • 使当前执行代码的线程进行等待. (把线程放到等待队列中)
  • 释放当前的锁
  • 满足一定条件时被唤醒, 重新尝试获取这个锁.

wait 要搭配 synchronized 来使用. 脱离 synchronized 使用 wait 会直接抛出异常.

在这里插入图片描述

wait 结束等待的条件:

  • 其他线程调用该对象的 notify 方法.
  • wait 等待时间超时 (wait 方法提供一个带有 timeout 参数的版本, 来指定等待时间).
  • 其他线程调用该等待线程的 interrupted 方法, 导致 wait 抛出 InterruptedException 异常.

注意事项:

  1. 调用wait()方法的前提是首先要获取该对象的锁(synchronize对象锁)

  2. 调用wait()方法会释放锁,本线程进入等待队列等待被唤醒,被唤醒后不是立即恢复执行,而是进入阻塞队列竞争锁

等待方法:

1.痴汉方法,死等,线程进入阻塞态(WAITING)直到有其他线程调用notify方法唤醒

在这里插入图片描述

2.等待一段时间,若在该时间内线程被唤醒,则继续执行,若超过相应时间还没有其他线程唤醒此线程,此线程不再等待,恢复执行。

在这里插入图片描述

调用wait方法之后:

在这里插入图片描述

三、唤醒方法notify()

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

  • 方法notify()也要在同步方法或同步块中调用,该方法是用来通知那些可能等待该对象的对象锁的其它线程,对其发出通知notify,并使它们重新获取该对象的对象锁。
  • 如果有多个线程等待,则有线程调度器随机挑选出一个呈 wait 状态的线程。(并没有 “先来后到”)
  • 在notify()方法后,当前线程不会马上释放该对象锁,要等到执行notify()方法的线程将程序执行完,也就是退出同步代码块之后才会释放对象锁

注意事项:

  • notify():随机唤醒一个处在等待状态的线程。
  • notifyAll():唤醒所有处在等待状态的线程。
  • 无论是wait还是notify方法,都需要搭配synchronized锁来使用(等待和唤醒,也是需要对象)

在这里插入图片描述

四、关于wait和notify内部等待问题(重要)

对于wait和notify方法,其实有一个阻塞队列也有一个等待队列

  • 阻塞队列表示同一时间只有一个线程能获取到锁,其他线程进入阻塞队列

  • 等待队列表示调用wait (首先此线程要获取到锁,进入等待队列,释放锁

举个栗子:

现有如下定义的等待线程任务

private static class WaitTask implements Runnable {
        private Object lock;
        public WaitTask(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
                // 此线程在等待lock对象的notify方法唤醒
                try {
                    lock.wait();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "等待结束,本线程继续执行");
            }
        }
    }

然后创建三个等待线程:

在这里插入图片描述

由于同一时间只有一个线程(随机调度)能获取到synchronized锁,所以会有两个线程没竞争到锁,从而进入了阻塞队列

这里假如t2先竞争到了锁,所以先会阻塞t1和t3:

在这里插入图片描述

又由于调用wait方法会释放锁,调用wait方法的线程t2就会进入等待队列,直到被notify唤醒或者超时自动唤醒。

在这里插入图片描述

然后此时lock对象已经被释放了,所以t1和t3 又可以去竞争这个锁了,就从阻塞队列里面竞争锁。

这里假如t3 竞争到了锁,阻塞队列只剩下t1:

在这里插入图片描述

然后t3运行到了wait方法,释放锁,然后进入等待队列

在这里插入图片描述

然后重复这些操作~~,最后t1,t2,t3 都进入了等待队列中,等待notify线程唤醒(这里假设notify要放在这些线程start后的好几秒后,因为notify线程也是和这些线程并发执行的,所以等待队列中的线程随时可能被唤醒

在这里插入图片描述

重点来了:

在等待队列中的线程,被notify唤醒之后,会直接回到阻塞队列去竞争锁!!!而不是直接唤醒~

举个栗子:

拿notifyAll()来举例,假如此时等待队列中有三个线程t1,t2,t3,那么调用notifyAll()会直接把它们三个直接从等待队列中进入到阻塞队列中:

在这里插入图片描述

然后再去竞争这个锁,去执行wait之后的代码~~

五、完整代码(仅供测试用)

private static class WaitTask implements Runnable {
        private Object lock;
        public WaitTask(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println(Thread.currentThread().getName() + "准备进入等待状态");
                // 此线程在等待lock对象的notify方法唤醒
                try {
                    lock.wait();
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println(Thread.currentThread().getName() + "等待结束,本线程继续执行");
            }
        }
    }
    private static class NotifyTask implements Runnable {
        private Object lock;
        public NotifyTask(Object lock) {
            this.lock = lock;
        }
        @Override
        public void run() {
            synchronized (lock) {
                System.out.println("准备唤醒");
                // 唤醒所有线程(随机)
                lock.notifyAll();
                System.out.println("唤醒结束");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object lock = new Object();
        Object lock2 = new Object();
        // 创建三个等待线程
        Thread t1 = new Thread(new WaitTask(lock),"t1");
        Thread t2 = new Thread(new WaitTask(lock),"t2");
        Thread t3 = new Thread(new WaitTask(lock),"t3");
       // 创建一个唤醒线程
        Thread notify = new Thread(new NotifyTask(lock2),"notify线程");
        t1.start();
        t2.start();
        t3.start();
        ;
        Thread.sleep(100);
        notify.start();
        // 当前正在执行的线程数
        Thread.sleep(2000);
        System.out.println(Thread.activeCount() - 1);
    }

六、wait和sleep方法的区别(面试题):

  1. wait方法是Object类提供的方法,需要搭配synchronized锁来使用,调用wait方法会释放锁,线程进入WAITING状态,等待被其他线程唤醒或者超时自动唤醒,唤醒之后的线程需要再次竞争synchronized锁才能继续执行。
  2. sleep方法是Thread类提供的方法,调用sleep方法的线程进入TIMED_WAITING状态,不会释放锁,时间到自动唤醒。

总结

以上就是多线程场景下wait和notify方法的详解和注意事项了,码字不易,有帮助的话别忘了关注博主,点赞+收藏哦~

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

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

相关文章

docker实战学习2022版本(六)之Dockerfile整合微服务实战

需求&#xff1a;通过idea新建一个普通微服务模块&#xff1b;然后通过Dockerfile发布微服务部署到docker容器 step1&#xff1a;新建一个springboot项目&#xff0c;添加依赖 <dependencies><!--SpringBoot通用依赖模块--><dependency><groupId>org.…

Clickhouse与Doris的区别

Doris使用较为简单&#xff0c;join功能更强大&#xff0c;运维更简单&#xff0c;灵活的扩容缩容&#xff0c;分布式更强&#xff0c;支持事务和幂等性导数 Clickhouse性能更佳&#xff0c;导入性能和单表查询性能更好&#xff0c;同时可靠性更好&#xff0c;支持非常多的表引…

谈一谈AI对人工的取代

文章目录AI绘画现在达到了什么水平&#xff1f;易用性怎么样&#xff1f;**缘起&#xff1a;2015年 用文字画画****2021年 Dalle 与 开源社区的程序员们****openAI与它并不open的Dalle****AI开源社区****Dream by [wombo](https://www.zhihu.com/search?qwombo&search_sou…

内农大《嵌入式基础》实验二 C语言进阶和Makefile

一、 实验目的 利用多文件编程&#xff0c;掌握Linux环境下C程序的编辑、编译、运行等操作。掌握Makefile文件的编写、变量及隐式规则和模式规则的应用。掌握Linux环境下main函数的参数。掌握各类指针的应用。 二、 实验任务与要求 根据实验要求编写C语言程序&#xff1b;写…

LiteIDE主题定制教程【续】

摘要&#xff1a;本篇文章是LiteIDE主题定制教程的续作&#xff0c;之所以会有这篇续作&#xff0c;是因为在写完那篇文章之后&#xff0c;我在使用过程中陆续发现了一些问题&#xff0c;以及一些可以优化的地方&#xff0c;我将这些内容作为补充放到这篇文章里。所有更新都已同…

<Linux系统复习>文件系统的理解

一、本章重点 1、磁盘的物理结构 2、磁盘文件如何存储&#xff1f; 3、目录的理解 4、创建一个文件做了什么&#xff1f; 5、删除一个文件做了什么&#xff1f; 6、软连接 7、硬链接 01 磁盘的物理结构 磁盘是硬件结构唯一的机械设备&#xff0c;它通过磁头来进行磁盘的读写&am…

LabVIEW前面板上的字体大小取决于操作系统

LabVIEW前面板上的字体大小取决于操作系统 创建了一个VI&#xff0c;其前面板使用了多个标签和文本。我发现Windows 7系统上的字体大小与Windows 10系统上的字体大小不同。这导致我的前面板看起来不像我希望在计算机上看到的那模样。如何使字体在所有Windows操作系统上变得相同…

【Linux_】权限

【Linux_】权限 心有所向&#xff0c;日复一日&#xff0c;必有精进专栏&#xff1a;《Linux_》作者&#xff1a;沂沐沐目录 【Linux_】权限 前言 Linux权限的概念&#xff08;是什么&#xff09;&#xff1f; 什么是权限&#xff1f; Linux权限管理 文件访问者的分类&am…

npm包学习

想开发自己的的工具包&#xff0c;那必然要借鉴一些常用的npm包来帮我们解决一些问题&#xff0c;下面就罗列一些在学习vue-cli实现原理时候遇到的一些依赖包吧。 1、chalk 用途&#xff1a;可以修改终端输出字符的颜色&#xff0c;类似css的color属性&#xff0c;npm地址&am…

100天精通Python(数据分析篇)——第62天:pandas常用统计方法与案例

文章目录每篇前言一、常用统计方法与案例1. 求和&#xff08;sum&#xff09;2. 求平均值&#xff08;mean&#xff09;3. 求最小值&#xff08;min&#xff09;4. 求最大值&#xff08;max&#xff09;5. 求中位数&#xff08;median&#xff09;6. 求众数&#xff08;mode&am…

jQuery网页开发案例:jQuery 其他方法--jQuery 拷贝对象,多库共存,jQuery 插件

jQuery 对象拷贝 如果想要把某个对象拷贝&#xff08;合并&#xff09; 给另外一个对象使用&#xff0c;此时可以使用 $.extend() 方法 语法&#xff1a; $.extend([deep], target, object1, [objectN]) 1. deep: 如果设为true 为深拷贝&#xff0c; 默认为false 浅拷贝 …

做减法才是真本事,别以为你很能学,做加法一点都不难

文章目录 顶级的高手才敢做减法 前言 一、做减法才是真本事 二、大数据梦想联盟活动开启 顶级的高手才敢做减法 前言 大多数人不懂&#xff0c;不会&#xff0c;不做&#xff0c;才是你的机会&#xff0c;你得行动&#xff0c;不能畏首畏尾 大数据等于趋势&#xff0c;一…

Vue中computed和watch区别

前言 vue中的computed和watch我们经常会用到&#xff0c;那么在什么场景下使用computed和watch&#xff0c;两者又有什么区别呢&#xff0c;傻傻分不清楚。记录一下&#xff0c;温故而知新&#xff01; computed computed是计算属性&#xff0c;基于data中声明过或者父组件传递…

makkefile文件自动化编译以及基础文件命令(补)

目录makefile文件&#xff1a;实现自动化编译基础文件命令find&#xff08;查找&#xff09;grep&#xff08;过滤&#xff09;| &#xff08;管道&#xff09;关机重启文件压缩解压分步压缩解压一步压缩解压makefile文件&#xff1a;实现自动化编译 文件名称必须是:makefile …

【day15】每日强训编程题——查找输入整数二进制中1的个数手套

查找输入整数二进制中1的个数_牛客题霸_牛客网 这道题非常简单&#xff0c;就一个思路&#xff1a; 按位与& 任何一个数按位与上1&#xff0c;如果这个数二进制的最后一位是1&#xff0c;那么按位与的结果就是1&#xff0c;否则就是0 代码思路&#xff1a;n按位与1后往右…

【splishsplash】PBD探究

上次我们探究了PBD是如何引入plishsplash的&#xff0c;以及其控制流。 https://blog.csdn.net/weixin_43940314/article/details/127569870 这次我们来讲如何在自己新建的类中控制PBD刚体。 上回说到 Simulator\PositionBasedDynamicsWrapper\PBDWrapper.cpp 中的 void PBD…

AXI协议详解(6)-原子访问

原子访问 本章介绍了 AXI 协议如何实现排他访问和锁定访问机制。 它包含以下部分&#xff1a; 原子访问排他访问锁定访问 6.1 原子访问 为了实现原子访问权限&#xff0c;ARLOCK[1:0] 或 AWLOCK[1:0] 信号提供排他访问和锁定访问。 表 6-1 显示了 ARLOCK[1:0] 和 AWLOCK[1:…

3.NLP基础:文本可视化简述

1.文本可视化的流程 文本可视化依赖于自然语言处理&#xff0c;因此词袋模型、命名实体识别、关键词抽取、主题分析、情感分析等是较常用的文本分析技术。文本分析的过程主要包括特征提取&#xff0c;通过分词、抽取、归一化等操作提取出文本词汇级的内容&#xff0c;利用特征…

Qt 集成 FFmpeg 实现颜色格式转换

目录 1. Qt 集成 FFmpeg 1.1 下载 FFmpeg 1.2 Qt 集成 FFmpeg 1.2.1 修改 .pro 文件 1.2.2 放入 dll 文件 1.2.3 代码中使用 FFmpeg 2. 图像格式转换 3. 预览 4. 项目地址 项目需要&#xff0c;写个小工具来实现图像颜色格式的转换&#xff0c;主要的 Feature 如下&am…

百度最强中文AI作画大模型

前言 最近文生图领域的发展可谓是分生水起&#xff0c;这主要是得益于最近大火的扩散模型&#xff0c;之前笔者也写过一篇关于文本生产3D模型的文章&#xff0c;大家感兴趣的可以穿梭&#xff1a; https://zhuanlan.zhihu.com/p/570332906 今天要给大家介绍的这一篇paper是百度…