java JUC 中 Object里wait()、notify() 实现原理及实战讲解

news2024/9/20 11:02:31

1.Object中的wait()实现原理

在进行wait()之前,就代表着需要争夺Synchorized,而Synchronized代码块通过javap生成的字节码中包含monitorenter和monitorexit两个指令。

当在进加锁的时候会执行monitorenter指令,执行该指令可以获取对象的monitor。同时在执行Lock.wait()的时候也必须持有monitor对象。

在多核环境下,多个线程有可能同时执行monitorenter指令,并获取lock对象关联的monitor,但只有一个线程可以和monitor建立关联,这个线程执行到wait方法时,wait方法会将当前线程放入wait set,使其进行等待直到被唤醒,并放弃lock对象上的所有同步声明,意味着该线程释放了锁,其他线程可以重新执行加锁操作,notify方法会选择wait set中任意一个线程进行唤醒,notifyAll方法会唤醒monitor的wait set中所有线程。执行完notify方法并不会立马唤醒等待线程。那么wait具体是怎么实现的呢?

首先在HotSpot虚拟机中,monitor采用ObjectMonitor实现,每个线程都具有两个队列,分别为free和used,用来存放ObjectMonitor。如果当前free列表为空,线程将向全局global list请求分配ObjectMonitor。

ObjectMonitor对象中有两个队列,都用来保存ObjectWaiter对象,分别是_WaitSet 和 _EntrySet。_owner用来指向获得ObjectMonitor对象的线程

ObjectWaiter对象是双向链表结构,保存了_thread(当前线程)以及当前的状态TState等数据, 每个等待锁的线程都会被封装成ObjectWaiter对象。

img

  • _WaitSet:处于wait状态的线程,会被加入到wait set;
  • _EntrySett:处于等待锁block状态的线程,会被加入到entry set;

1.1 wait方法实现

lock.wait()方法最终通过ObjectMonitor的 wait(jlong millis, bool interruptable, TRAPS)实现

1、将当前线程封装成ObjectWaiter对象node

2、通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中

3、通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象

4、最终底层的park方法会挂起线程

ObjectSynchorizer::wait方法通过Object对象找到ObjectMonitor对象来调用方法 ObjectMonitor::wait(),通过调用ObjectMonitor::AddWaiter()可以把新建的ObjectWaiter对象,放入到_WaitSet队列的末尾,然后在ObjectMonitor::exit释放锁,接着通过执行thread_ParkEvent->park来挂起线程,也就是进行wait。

2. Object对象中的wait,notify,notifyAll的理解

wait,notify,notifyAll 是定义在Object类的实例方法,用于控制线程状态,在线程协作时,大家都会用到notify()或者notifyAll()方法,其中wait与notify是java同步机制中重要的组成部分,需要结合与synchronized关键字才能使用,在调用一个Object的wait与notify/notifyAll的时候,必须保证调用代码对该Object是同步的,也就是说必须在作用等同于synchronized(object){…}的内部才能够去调用obj的wait与notify/notifyAll三个方法,否则就会报错:java.lang.IllegalMonitorStateException:current thread not owner(意思是因为没有同步,所以线程对对象锁的状态是不确定的,不能调用这些方法)。

wait的目的就在于暴露出对象锁,所以需要保证在lock的同步代码中调用lock.wait()方法,让其他线程可以通过对象的notify叫醒等待在该对象的等该池里的线程。同样notify也会释放对象锁,在调用之前必须获得对象的锁,不然也会报异常。所以,在线程自动释放其占有的对象锁后,不会去申请对象锁,只有当线程被唤醒的时候或者达到最大的睡眠时间,它才再次争取对象锁的权利

主要方法:

(1).wait()

等待对象的同步锁,需要获得该对象的同步锁才可以调用这个方法,否则编译可以通过,但运行时会收到一个异常:IllegalMonitorStateException。调用任意对象的 wait() 方法导致该线程阻塞,该线程不可继续执行,并且该对象上的锁被释放。

(2).notify()

唤醒在等待该对象同步锁的线程(只唤醒一个,如果有多个在等待),注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。调用任意对象的notify()方法则导致因调用该对象的 wait()方法而阻塞的线程中随机选择的一个解除阻塞(但要等到获得锁后才真正可执行)。

(3).notifyAll()

唤醒所有等待的线程,注意唤醒的是notify之前wait的线程,对于notify之后的wait线程是没有效果的。

3. wait 实战

通过一个实例来看一下实际的效果,开启两个线和,一个线程 打印1到52的数字,一个打印A到Z的字母,要求,打印两个数,打印一个字母,这样交替顺序打印,代码如下:

public class ShuZiZiMuThread {
    public static void main(String[] args) {
        Object object = new Object();
        shuzi shuzi = new shuzi(object);
        zimu zimu = new zimu(object);
        Thread t = new Thread(shuzi);
        t.setName("shuzi");
        Thread t1 = new Thread(zimu);
        t1.setName("zimu");
        t.start();//数字线程先运行
        t1.start();
    }
}
class shuzi implements Runnable{
    private Object object;
    //声明类的引用
    public shuzi(Object object) {
        this.object = object;
    }
    public void run() {
        synchronized (object) {//上锁
            for(int i=1;i<53;i++){
                System.out.print(i+",");
                if(i%2==0){
                    object.notifyAll();//唤醒其它争夺权限的线程
                    try {
                        object.wait();//释放锁,进入等待
                        System.out.println("数字打印类打全打印当前对象拥有对象锁的线程"+Thread.currentThread().getName());//输出当前拥有锁的线程名称
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
}
class zimu implements Runnable{
    private Object object;
    public zimu(Object object) {
        this.object = object;
    }
    public void run() {
        synchronized (object) {
            for(int j=65;j<91;j++){
                char c = (char)j;
                System.out.print(c);
                object.notifyAll();//唤醒其它争夺权限的线程
                try {
                    object.wait();//释放锁,进入等待
                    System.out.println("字母打印类打全打印当前对象拥有对象锁的线程"+Thread.currentThread().getName());//输出当前拥有锁的线程名称
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

实际运行的结果 :

1,2,A数字打印类打印当前对象拥有对象锁的线程shuzi
3,4,字母打印类打印当前对象拥有对象锁的线程zimu
B数字打印类打印当前对象拥有对象锁的线程shuzi
5,6,字母打印类打印当前对象拥有对象锁的线程zimu
C数字打印类打印当前对象拥有对象锁的线程shuzi
7,8,字母打印类打印当前对象拥有对象锁的线程zimu
D数字打印类打印当前对象拥有对象锁的线程shuzi
9,10,字母打印类打印当前对象拥有对象锁的线程zimu
E数字打印类打印当前对象拥有对象锁的线程shuzi
11,12,字母打印类打印当前对象拥有对象锁的线程zimu
F数字打印类打印当前对象拥有对象锁的线程shuzi
13,14,字母打印类打印当前对象拥有对象锁的线程zimu
G数字打印类打印当前对象拥有对象锁的线程shuzi
15,16,字母打印类打印当前对象拥有对象锁的线程zimu
H数字打印类打印当前对象拥有对象锁的线程shuzi
17,18,字母打印类打印当前对象拥有对象锁的线程zimu
I数字打印类打印当前对象拥有对象锁的线程shuzi
19,20,字母打印类打印当前对象拥有对象锁的线程zimu
J数字打印类打印当前对象拥有对象锁的线程shuzi
21,22,字母打印类打印当前对象拥有对象锁的线程zimu
K数字打印类打印当前对象拥有对象锁的线程shuzi
23,24,字母打印类打印当前对象拥有对象锁的线程zimu
L数字打印类打印当前对象拥有对象锁的线程shuzi
25,26,字母打印类打印当前对象拥有对象锁的线程zimu
M数字打印类打印当前对象拥有对象锁的线程shuzi
27,28,字母打印类打印当前对象拥有对象锁的线程zimu
N数字打印类打印当前对象拥有对象锁的线程shuzi
29,30,字母打印类打印当前对象拥有对象锁的线程zimu
O数字打印类打印当前对象拥有对象锁的线程shuzi
31,32,字母打印类打印当前对象拥有对象锁的线程zimu
P数字打印类打印当前对象拥有对象锁的线程shuzi
33,34,字母打印类打印当前对象拥有对象锁的线程zimu
Q数字打印类打印当前对象拥有对象锁的线程shuzi
35,36,字母打印类打印当前对象拥有对象锁的线程zimu
R数字打印类打印当前对象拥有对象锁的线程shuzi
37,38,字母打印类打印当前对象拥有对象锁的线程zimu
S数字打印类打印当前对象拥有对象锁的线程shuzi
39,40,字母打印类打印当前对象拥有对象锁的线程zimu
T数字打印类打印当前对象拥有对象锁的线程shuzi
41,42,字母打印类打印当前对象拥有对象锁的线程zimu
U数字打印类打印当前对象拥有对象锁的线程shuzi
43,44,字母打印类打印当前对象拥有对象锁的线程zimu
V数字打印类打印当前对象拥有对象锁的线程shuzi
45,46,字母打印类打印当前对象拥有对象锁的线程zimu
W数字打印类打印当前对象拥有对象锁的线程shuzi
47,48,字母打印类打印当前对象拥有对象锁的线程zimu
X数字打印类打印当前对象拥有对象锁的线程shuzi
49,50,字母打印类打印当前对象拥有对象锁的线程zimu
Y数字打印类打印当前对象拥有对象锁的线程shuzi
51,52,字母打印类打印当前对象拥有对象锁的线程zimu
Z数字打印类打印当前对象拥有对象锁的线程shuzi

结果分析:

通过结果可以看出:

在字母打一打印类里 调用完

通过结果可以看出:

在字母打一打印类里 调用完

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

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

相关文章

前端与HTML

本节课程围绕“前端要解决的基本问题”及“什么是 HTML ”两个基本问题展开&#xff0c;了解 HTML 高效的编写原则。 什么是前端 使用web技术栈解决多端的人机交互问题 技术栈 html&#xff08;内容&#xff09; css &#xff08;样式&#xff09;javascript &#xff08;行…

linux部署KubeSphere和k8s集群

上一篇文章讲述了在单个节点上安装 KubeSphere和k8s&#xff0c;这节主要讲解k8s多节点集群部署 准备环境&#xff1a;Alibaba Cloud Linux系统3台机器第一步&#xff1a;设置主机名称hostname--(3台机器都设置) hostnamectl set-hostname master hostnamectl set-hostname nod…

智云通CRM:为什么你总是在请客,但业绩却上不来?

王总是一位企业老板&#xff0c;社会资源比较好&#xff0c;在过去的一年里&#xff0c;他新代理的一个保健品的项目&#xff0c;需要销售产品和招募合伙人。他想利用自己的人脉资源做销售&#xff0c;但他的销售过程并不顺利&#xff0c;在连续主动邀约之后效果不佳。 于是他…

2023/1/15 JS-变量提升与函数提升 执行上下文

1 变量提升与函数提升 变量声明提升 通过 var 声明的变量&#xff0c;在声明语句之前就可以访问到 - 值: undefined <script>console.log(a); // undefinedvar a 10 </script>函数声明提升 通过 function 声明的函数, 在声明语句之前就可以直接调用 - 值: 函数…

走近软件生态系统

生态系统&#xff08;Ecosystem&#xff09;原本是一个生物学术语&#xff0c;意思是由一些生命体相互依存、相互制约而形成的大系统&#xff0c;就像我们学生时代在生物学课堂上学到的那样。隐喻无处不在&#xff0c;人们把这个术语移植到了 IT 领域中来&#xff0c;比如我们常…

计算机基础(六):静态链接与动态链接

上一篇文章简单概括了 C语言程序经过编译&#xff0c;生成汇编语言、机器语言的基本过程。今天主要介绍其中链接阶段的实现思路。 静态链接 静态链接是将被依赖的代码片段复制到执行程序中&#xff0c;进行代码整合。因为我们在汇编代码中看到的是具体的符号&#xff0c;而且…

电路方案分析(十七)TI远程声控参考设计

远程声控参考设计 描述 CC2650远程控制设计为基于ZigBeeRF4CE™兼容的软件架构RemeTI™或蓝牙低能耗软件堆栈的快速测试、评估和开发远程控制应用程序提供了最佳基础。 该方案设计包含了CC2560远程控制的原理图和布局文件&#xff0c;以及一个演示了使用RF4CE和低能耗蓝牙的…

层次分析法和熵值法经典实操案例+数据

1、数据来源&#xff1a;无 2、时间跨度&#xff1a;无 3、区域范围&#xff1a;无 4、指标说明&#xff1a; 层次分析法&#xff08;Analytic Hierarchy Process&#xff0c;简称AHP&#xff09;是美国运筹学家、匹兹堡大学T. L. Saaty教授在20世纪70年代初期提出的&#…

《Buildozer打包实战指南》第二节 安装Kivy和Buildozer

目录 2.1 安装Kivy 2.2 安装Buildozer 2.3 验证安装 2.4 一点建议 Python是Ubuntu系统中自带的&#xff0c;我们在桌面上右键打开终端&#xff0c;然后输入python3 --version就可以看到Ubuntu系统中的Python版本了。 可以看到&#xff0c;Python的版本是3.10.6。虽然Python…

【Go基础】结构体

1. 结构体引入 Golang也支持面向对象编程&#xff0c;但是和传统的面向对象有区别&#xff0c;并不是像Java、C那样纯粹的面向对象语言&#xff0c;而是通过特别的手段实现面向对象特点。 Golang没有类(Class)的概念&#xff0c;但是提供了结构体(struct)&#xff0c;和其他编…

Nacos的学习

Nacos的学习 1、下载地址 https://github.com/alibaba/nacos/releases/tag/2.1.1 在bin目录中输入命令 startup.cmd -m standalone 输入localhost:8848/nacos 账号&#xff1a;nacos&#xff0c;密码&#xff1a;nacos 2、Spring与Nacos &#xff08;1&#xff09;新增一个配…

100天精通Python(数据分析篇)——第72天:Pandas文本数据处理方法之判断类型、去除空白字符、拆分和连接

文章目录每篇前言一、Python字符串内置方法1. 判断类型2. 去除空白字符3. 拆分和连接二、Pandas判断类型1. str.isspace()2. str.isalnum()3. str.isalpha()4. str.isdecimal()5. str.isdigit()6. str.isnumeric()7. str.istitle()8. str.islower()9. str.isupper()三、Pandas去…

音视频技术开发周刊 | 279

每周一期&#xff0c;纵览音视频技术领域的干货。新闻投稿&#xff1a;contributelivevideostack.com。基于NeRF的APP上架苹果商店&#xff01;照片转3D只需一部手机这个名叫Luma AI的“NeRF APP”&#xff0c;正式上架App Store后爆火。反 AiArt 运动中两件匪夷所思的蠢事Redd…

Elastic:使用 Postman 来访问

Elastic&#xff1a;使用 Postman 来访问 学习资料 Elastic&#xff1a;使用 Postman 来访问 Elastic Stack 当我们配置好elasticsearch的SSL之后&#xff0c;我们用网页https访问&#xff0c;输入账户及密码之后&#xff0c;可以成功访问数据。 但是用postman时&#xff0c;我…

2023/1/15 JS-闭包问题研究

1 举个栗子分析执行上下文 1: let a 3 2: function addTwo(x) { 3: let ret x 2 4: return ret 5: } 6: let b addTwo(a) 7: console.log(b)为了理解 JavaScript 引擎是如何工作的&#xff0c;让我们详细分析一下&#xff1a; 在第 1 行&#xff0c;我们在全局执行上…

Linux chattr命令

Linux chattr命令Linux 命令大全Linux chattr命令用于改变文件属性。这项指令可改变存放在ext2文件系统上的文件或目录属性&#xff0c;这些属性共有以下8种模式&#xff1a;a&#xff1a;让文件或目录仅供附加用途。b&#xff1a;不更新文件或目录的最后存取时间。c&#xff1…

从上到下看内存

从上到下看内存 1. 本篇目录 内存条,总线,DMAC 内存管理内存分类 内存相关的系统调用 java中的内存 2. 内存条,总线,DMAC 内存条 内存条&#xff1a;内存条其实是非常常见的一个组件。内存条是插在主板上的。 总线 内存条插好以后&#xff0c;计算机之间要进行交互。…

Linux 中断子系统(四):GIC中断初始化

以我手中的 imx6ull开发板为例。 如果使用设备树的话就需要在设备树中设置好中断属性信息,Linux 内核通过读取设备树中的中断属性信息来配置中断。对于中断控制器而言,设备树绑定信息参考文档 Documentation/devicetree/bindings/arm/gic.txt。 打开 imx6ull.dtsi 文件,其…

UDS诊断系列介绍12-11服务

本文框架1. 系列介绍1.1 11服务概述2. 11服务请求与应答2.1 11服务请求2.2 11服务正响应2.3 11服务否定响应3. Autosar系列文章快速链接1. 系列介绍 UDS&#xff08;Unified Diagnostic Services&#xff09;协议&#xff0c;即统一的诊断服务&#xff0c;是面向整车所有ECU的…

三种方法解决React类组件中this指向问题

从onClick事件不加括号说起 import React from react import ./App.css class TestComponent extends React.Component {clickHandler () {console.log(111)console.log(this指向&#xff1a;, this)}render () {return (<button onClick{this.clickHandler()}>点击我&l…