彻底搞懂Java的等待-通知(wait-notify)机制

2023-11-07

线程的生命周期转换

640?wx_fmt=jpeg

  1. 新建状态(New):新建一个线程对象。

  2. 就绪/可运行状态(Runnable):线程对象创建后,其他线程调用了该对象的start方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。

  3. 运行状态(Running):就绪状态的线程获得CPU并执行程序代码。

  4. 阻塞状态(Blocked):阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:

    1. 等待阻塞:运行的线程执行wait方法,JVM会把该线程放入等待池中。(wait会释放持有的锁)

    2. 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。

    3. 其他阻塞:运行的线程执行sleep或join方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep的状态超时、join等待线程终止或者超时、以及I/O处理完毕时,线程重新转入就绪状态。

  5. 死亡状态(Dead):线程执行完成或者因异常退出run方法,该线程结束生命周期。

wait()与notify()

  • wait():使调用该方法的线程释放共享资源锁,然后从运行状态退出,进入等待队列,直到被再次唤醒。

  • wait(long):超时等待一段时间,这里的参数时间是毫秒,也就是等待长达n毫秒,如果没有通知就超时返回。

  • wait(long,int):对于超时时间更细力度的控制,单位为纳秒。

  • notify():随机唤醒等待队列中等待同一共享资源的一个线程,并使该线程退出等待队列,进入可运行状态,也就是notify()方法仅通知一个线程。

  • notifyAll():使所有正在等待队列中等待同一共享资源的全部线程退出等待队列,进入可运行状态。此时,优先级最高的那个线程最先执行,但也有可能是随机执行,这取决于JVM虚拟机的实现。

什么是等待/通知机制

通俗来讲:

等待/通知机制在我们生活中很常见,一个形象的例子就是厨师和服务员之间就存在等待/通知机制。

  • 厨师做完一道菜的时间是不确定的,所以菜到服务员手中的时间也是不确定的。

  • 服务员就需要去“等待(wait)”。

  • 厨师把菜做完之后,按一下铃进行“通知(nofity)”。

  • 服务员听到铃声之后就知道菜做好了,就可以去端菜了。

使用专业术语讲:

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

多线程轮流打印代码示例

通过一道面试题就能完全明白wait/notify机制。

问题:写两个线程,一个线程打印1-52,另一个线程打印A-Z,打印结果为12A34B...5152Z

class Print {

    private int flag = 1;//信号量。当值为1时打印数字,当值为2时打印字母
    private int count = 1;

    public synchronized void printNum() {
        if (flag != 1) {
            //打印数字
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.print(2 * count - 1);
        System.out.print(2 * count);
        flag = 2;
        notify();
    }

    public synchronized void printChar() {
        if (flag != 2) {
            //打印字母
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.print((char) (count - 1 + 'A'));
        count++;//当一轮循环打印完之后,计数器加1
        flag = 1;
        notify();
    }
}

public class Test {
    public static void main(String[] args) {
        Print print = new Print();
        new Thread(() -> {
            for (int i = 0; i < 26; i++) {
                print.printNum();
            }
        }).start();
        new Thread(() -> {
            for (int i = 0; i < 26; i++) {
                print.printChar();
            }
        }).start();
    }
}

FAQ

wait、notify/notifyAll和sleep的区别与联系

  1. 前三个方法是Object的本地final方法,sleep方法是Thead类的静态方法。

  2. wait使当前线程阻塞,前提是必须先获得锁,所以只能在synchronized锁范围内里使用wait、notify/notifyAll方法,而sleep可以在任何地方使用。

  3. sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。

  4. notify和wait的顺序不能错,如果A线程先执行notify方法,B线程在执行wait方法,那么B线程是无法被唤醒的。

notify和notifyAll的区别

  • notify方法只唤醒一个等待(对象的)线程并使该线程开始执行。所以如果有多个线程等待一个对象,这个方法只会唤醒其中一个线程,选择哪个线程取决于操作系统对多线程管理的实现。

  • notifyAll会唤醒所有等待(对象的)线程,尽管哪一个线程将会第一个处理取决于操作系统的实现。如果当前情况下有多个线程需要被唤醒,推荐使用notifyAll方法。

sleep和wait的区别

  1. 当线程执行sleep方法时,不会释放当前的锁(如果当前线程进入了同步锁),也不会让出CPU。sleep(milliseconds)可以用指定时间使它自动唤醒过来,如果时间不到只能调用interrupt方法强行打断。

  2. 当线程执行wait方法时,会释放当前的锁,然后让出CPU,进入等待状态。只有当notify/notifyAll被执行时候,才会唤醒一个或多个正处于等待状态的线程,然后继续往下执行,直到执行完synchronized代码块的代码或是中途遇到wait() ,再次释放锁。

Thread.Sleep(0)的作用是“触发操作系统立刻重新进行一次CPU竞争”

notify/notifyAll的执行只是唤醒沉睡的线程,而不会立即释放锁,必须执行完notify()方法所在的synchronized代码块后才释放。所以在编程中,尽量在使用了notify/notifyAll()后立即退出临界区。

改变线程优先级

每个线程执行时都具有一定的优先级,优先级高的线程获得较多的执行机会,而优先级低的线程则获得较少的执行机会。每个线程默认的优先级都与创建它的父线程的优先级相同。Thread类提供了setPriority(int newPriority)来设置指定线程的优先级,提供了getPriority()来返回指定线程的优先级。

JAVA提供了10个优先级级别,但这些优先级需要操作系统支持。不同的操作系统上的优先级并不相同,而且也不能很好的和JAVA的10个优先级对应,比如:Windows 2000仅提供了7个优先级。因此,写代码的时候应该尽量避免直接为线程指定优先级,而应该使用MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY这三个静态常量来设置优先级,这样才能保证程序有最好的可移植性。

一个线程两次调用start方法会出现什么情况?

Java的线程是不允许启动两次的,第二次调用必然会抛出IllegalThreadStateException,这是一种运行时异常。

park()与unpark()

concurrent包是基于AQS(AbstractQueuedSynchronizer)框架的,AQS框架借助于两个类:

  1. Unsafe(提供CAS操作)

  2. LockSupport(提供park/unpark操作)

LockSupport.park()和LockSupport.unpark(Thread thread)调用的是Unsafe中的native代码。

有关AQS,可以查看笔者之前的博客,快速了解基于AQS实现的Java并发工具类

park与unpark的特点

  1. park/unpark的设计原理核心是“许可”(permit):park是等待一个许可,unpark是为某线程提供一个许可。permit不能叠加,也就是说permit的个数要么是0,要么是1。也就是不管连续调用多少次unpark,permit也是1个。线程调用一次park就会消耗掉permit,再一次调用park又会阻塞住。如果某线程A调用park,那么除非另外一个线程调用unpark(A)给A一个许可,否则线程A将阻塞在park操作上。

  2. unpark可以先于park调用。在使用park和unpark的时候可以不用担心park的时序问题造成死锁。相比之下,wait/notify存在时序问题,wait必须在notify调用之前调用,否则虽然另一个线程调用了notify,但是由于在wait之前调用了,wait感知不到,就造成wait永远在阻塞。

  3. park和unpark调用的时候不需要获取同步锁。

park与unpark的优点

与Object类的wait/notify机制相比,park/unpark有两个优点:

  1. 以thread为操作对象更符合阻塞线程的直观定义。

  2. 操作更精准,可以准确地唤醒某一个线程(notify随机唤醒一个线程,notifyAll唤醒所有等待的线程),增加了灵活性。

底层实现原理

在Linux系统下,是用的Posix线程库pthread中的mutex(互斥量),condition(条件变量)来实现的。
mutex和condition保护了一个_counter的变量,当park时,这个变量被设置为0,当unpark时,这个变量被设置为1。

更多内容,欢迎关注微信公众号:全菜工程师小辉~

640?wx_fmt=png

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

彻底搞懂Java的等待-通知(wait-notify)机制 的相关文章

  • 谈谈数据的增量更新

    谈谈数据的增量更新 在数据同步的过程中 必然会遇到数据增量更新的需求 但如果没有一个有效的数据增量更新的设计与机制 可能每次上游数据更新时 你都需要将全量的数据同步一遍 即使只有1 的数据发生了更新 全量数据同步在数据首次同步的时候是需要的
  • source insight3.5 unable to create the project directory file

    在开始 gt 解除锁定的工程就可以了
  • 基于Docker快速搭建Hadoop集群和Flink运行环境

    前言 搭建集群 环境升级 配置Hadoop 配置Flink 打包镜像 启动集群 前言 本文主要讲 基于Docker在本地快速搭建一个Hadoop 2 7 2集群和Flink 1 11 2运行环境 用于日常Flink任务运行测试 前任栽树 后
  • Socks5代理IP在跨境电商与游戏中的应用

    随着互联网的迅猛发展 网络已经成为人们日常生活不可或缺的一部分 在这个数字化时代 跨境电商和网络游戏产业蓬勃发展 但伴随而来的是网络安全的威胁与挑战 本文将介绍Socks5代理IP技术 探讨它在网络安全 跨境电商以及游戏中的关键作用 以确保
  • PF_NETLINK应用实例NETLINK_KOBJECT_UEVENT具体实现--udev实现原理

    相对于linux来说 udev还是一个新事物 然而 尽管它03年才出现 尽管它很低调 J 但它无疑已经成为linux下不可或缺的组件了 udev是什么 它是如何实现的 最近研究Linux设备管理时 花了一些时间去研究udev的实现 udev
  • make: *** No rule to make target `build‘, needed by `default‘. Stop.

    解决Centos7 解决安装Nginx编辑make make install的不成功 make No rule to make target build needed by default Stop 解决方案 1 安装下面配置 yum y
  • Qt信号与槽七种连接方式

    Qt信号与槽七种连接方式 1 F3 F4编辑 拖入按键 按F4选择按键部位 拖动按键部位至mainwindow界面空白位置 并按如下操作 图1 2 signal slots的Edit方式 如图2 图2 3 QT4下的方式 1 connect
  • 才华战胜资本,原创受到尊重(抄袭事件后续)

    你敢相信吗 别人剽窃了我的课程 居然还要告我侵犯对方名誉 吾本小镇做题家 侥幸考入理工大 水杉林赏二月兰 梧桐树下闻桂花 感恩福报996 披星戴月写代码 专利 软著 写博客 半个嵌入式专家 穿上长衫笑寒酸 脱下长衫忙油盐 ad PCB 嘉立
  • 【OpenCV4】使用 normalize() 进行归一化(c++)

    函数原型 void cv normalize InputArray src InputOutputArray dst double alpha 1 double beta 0 int norm type NORM L2 int dtype
  • iptables详解及应用(史上最全)

    1 1 iptables概念 从逻辑上讲 防火墙可以大体分为主机防火墙和网络防火墙 主机防火墙 针对于单个主机进行防护 网络防火墙 往往处于网络入口或边缘 针对于网络入口进行防护 服务于防火墙背后的本地局域网 网络防火墙和主机防火墙并不冲突
  • matplotlib绘制柱状图(普通、堆叠、左右分布)

    文章目录 1 堆叠图 2 左右图 示例代码 3 堆叠 左右 示例代码 不论是堆叠 还是左右分布 其实他们都是柱状图bar 只是根据参数的不同 有不同的显示位置 多个bar呈现在一起就看上去像是堆叠起来 或是左右分布了 因此只需要控制bar的
  • Python3学习(七):模块

    Python3 模块 把一些可以反复使用的代码存放在文件中 为一些脚本或者交互式的解释器实例使用 这个文件被称为模块 模块是一个包含所有你定义的函数和变量的文件 其后缀名是 py 模块可以被别的程序引入 以使用该模块中的函数等功能 类似于C
  • JVM 如何自动完成垃圾回收

    一 简介 思考一个问题 在 java 里面我们 new 一个对象 等到程序结束后 这个对象就被自动回收了 完成这项工作只需要确定 哪些内存需要回收 什么时候回收 如何回收 接下来我们详细的解释下这三个问题 二 哪些内存需要回收 由于程序计数
  • 判断大小端存储两种办法

    1 强制转换 给定 int类型的变量a 赋值为1 1的16进制为 00 00 00 01 若小端存储则a中存储为 01 00 00 00 大端存储为 00 00 00 01 则可以取出a的地址强转为char 类型 char a来判断值为0
  • 2023最新最全git安装教程,保姆级手把手式安装!!!

    目录 一 git简介 二 安装过程 1 首先进入git的官网 https git scm com 然后选择Downloads 2 接着选择与自己电脑系统对应的下载选项 我的电脑是windows7的系统 因此选择windows 3 进去之后
  • [导入]TOMPDA WAP新闻订阅教程

    要浏览本条信息请点击文章标题 文章来源 http www wapkf com article other wap 2006 20060511241 html 转载于 https www cnblogs com 200831856 artic
  • 有哪些值得推荐的好用视频剪辑软件?

    首先 我们不管是工作需要 还是做自媒体 一款用着顺手的视频剪辑软件必不可少 经常看到很多人说我是小白 但又想学习视频剪辑 该如何选择适合自己的视频剪辑软件呢 看到抖音 小红书里面很多有意思的视频 自己也想剪辑试试 但又不知从何下手 话不多说
  • HuggingFace——Accelerate的使用

    Overview Accelerate is a library that enables the same PyTorch code to be run across any distributed configuration by ad

随机推荐

  • 20171207编写一个程序,只接受正整数的输入,然后显示所有小于或等于该数的素数。

    素数 在大于1的整数中 只能被1和这个数本身整除的数 思路 用一个标志sign 来标记出素数 include
  • flink学习40:tableAPI的扫描、投影、过滤、列操作

    from用法 select用法 as用法 where用法 filter用法 列操作 增 删 改
  • 【单例模式】

    单例模式 单例模式常见的几种方法 饿汉式 懒汉式DCL 懒汉式内部类 单例模式常见的几种方法 饿汉式 饿汉式 private final static SingletonPattern singletonPattern new Single
  • centos7 升级openssl1.1.1g、openssh8.6p1小记

    系统版本 CentOS Linux release 7 6 1810 Core 默认版本 OpenSSH 7 4p1 OpenSSL 1 0 2k fips 升级版本 OpenSSH 8 6p1 OpenSSL 1 1 1g 1 安装步骤
  • windows10中安装ubuntu双系统时出现unable to find a medium containing a live file system解决办法

    在ubuntu官网上下载最新的18 04 1LTS版本 通过rufus软件将其写入U盘中 但在电脑安装时出现如下错误 经搜索得到如下信息 原贴链接 只需在安装进行到如下界面时 拔掉U盘再插上即可解决问题
  • 我使用OpenCvSharp的一些坑,我的使用心得

    首先是关于 copyto 的操作郁闷 资源图片 需要 是正方形 或者 宽 大于高经我测试 长宽 大小的情况 还是需要跟背景有相应的一致性 比如如果背景 是长大于宽 则资源文件 也需要长大于宽 反之亦然 正方形的图片 则无此要求 要比背景图片
  • Mybatis高级映射

    Mybatis高级映射本质上来说是多个表的联合查询过程 订单数据模型分析思路 数据表 用户表user 记录了购买商品的用户信息 订单表orders 记录了用户创建的订单 购买商品的订单 订单明细表orderdatail 记录了订单的详细信息
  • 玩转Netty,从“Hello World”开始

    大家好 我是老三 之前里 我们讨论了Java的三种IO模型 提到了网络通信框架Netty 它简化和优化了NIO的使用 这期 我们正式开始走近Netty 为什么要用Netty 首先当然是NIO的使用 本身比较复杂 而且还存在一些问题 除此之外
  • Ubuntu 软件包管理详解

    Ubuntu 软件包管理详解 Ubuntu 方便宜用 最值得让人称道的便是其安装软件的方式 一条命令 sudo apt get install xxx 就几乎能帮你搞定所有的软件安装难题 但是有时你可能有这样的需求 查看某个软件包是否安装
  • 使用Object.setPrototypeOf()设置对象的原型

    此方法可以设置对象的原型 Object setPrototypeOf方法是针对对象实例的 而不是构造函数 类 此方法修改的是对象实例的内部属性 prototype 也就是 proto 属性所指向的对象 它只是修改了特定对象上的原型对象 对于
  • Unity中,实现鼠标点击物体,触发事件

    对于UI 很容易能够实现鼠标点击 从而触发事件 但是对于游戏中的物体 则需要多进行一些操作 原理很简单 就是由鼠标点击处发射线 与游戏物体发生碰撞 碰撞到的物体 就是你点击到的物体 具体操作如下 对你的Camera 摄像机 添加 Physi
  • python基础(三)

    模块相关基础 1 1模块的格式 usr bin env python3 coding utf 8 这是一个注释 author lnssm import sys def test args sys argv if len args 1 pri
  • uView 2.0 http请求封装基本使用

    uview2 0 http封装 根据官网填写即可 注意你的路径跟官网的不一样需要改动 网址 https www uviewui com js http html 封装过程 此处示范的是get请求 新建src config request j
  • Bandicam v6.2.4.2083 班迪录屏软件解锁VIP中文便携版

    4K超清屏幕录像 Bandicam 绿色正式版已集成授权信息 自动屏蔽联网验证授权 启动即为已授权版 无试用版任何的限制 录制时间没限制 录制大于十分钟的视频没有水印 最好用的电脑录屏软件 Bandicam班迪录屏 Bandicam 班迪录
  • 设计模式——Go语言(Golang)版:23_访问者模式

    1 介绍 表示一个作用于某对象结构中的各元素的操作 它使你可以在不改变各元素的类的前提下定义作用于这些元素的新操作 访问者模式 Visitor 是一种操作一组对象的操作 它的目的是不改变对象的定义 但允许新增不同的访问者 来定义新的操作 访
  • 【C语言】杨氏矩阵

    题目描述 有一个数字矩阵 矩阵的每行从左到右是递增的 矩阵从上到下是递增的 请编写程序在这样的矩阵中查找某个数字是否存在 要求 时间复杂度小于O N 思路1 可以采用遍历方式一个个查找 但是这样时间复杂度为O N 不满足题目要求 思路2 先
  • onedrive同步任意文件夹

    需求描述 想要通过onedrive同步备份一个工作文件夹 依次打开OneDrive 设置 备份 管理备份 发现默认只能备份特定的文件夹 如下所示 而我只想备份文档中的一个子文件夹 OneDrive默认5G空间 应该够了 解决办法 1 以管理
  • 30个jQuery按钮悬停动画

    超炫酷的30个jQuery按钮悬停动画 1 028 人浏览 发表回复 按钮插件是最常见的jQuery插件之一 因为它用途广泛 而且配置起来最为方便 今天我们要分享的是30个超炫酷的jQuery悬停按钮动画 当我们将鼠标滑过按钮时 按钮的背景
  • 组网方案设计,运用Mesh组网实现无缝漫游!

    在当今社会 无线网络已经成为了人们在工作 学习 娱乐生活中必不可少的一部分 但一台路由器的信号范围是有限的 随着距离的增加就会出现信号变弱导致的网速慢 网络卡顿甚至断网的情况 所以在一些面积较广 障碍较多 结构较复杂的场景下就需要安装两个乃
  • 彻底搞懂Java的等待-通知(wait-notify)机制

    线程的生命周期转换 新建状态 New 新建一个线程对象 就绪 可运行状态 Runnable 线程对象创建后 其他线程调用了该对象的start方法 该状态的线程位于可运行线程池中 变得可运行 等待获取CPU的使用权 运行状态 Running