JVM 源码分析之 Object.wait notify 实现

本贴最后更新于 2316 天前,其中的信息可能已经时移俗易

最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提供一个稳定的基础,Object 作为 java 中所有对象的基类,其存在的价值不言而喻,其中 wait 和 notify 方法的实现多线程协作提供了保证。

public class WaitNotifyCase {

public static void main(String[] args) { final Object lock = new Object(); new Thread(new Runnable() { @Override public void run() { System.out.println("thread A is waiting to get lock"); synchronized (lock) { try { System.out.println("thread A get lock"); TimeUnit.SECONDS.sleep(1); System.out.println("thread A do wait method"); lock.wait(); System.out.println("wait end"); } catch (InterruptedException e) { e.printStackTrace(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { System.out.println("thread B is waiting to get lock"); synchronized (lock) { System.out.println("thread B get lock"); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } lock.notify(); System.out.println("thread B do notify method"); } } }).start(); }

}

执行结果:

thread A is waiting to get lock

thread A get lock

thread B is waiting to get lock

thread A do wait method

thread B get lock

thread B do notify method

wait end

前提:由同一个 lock 对象调用 wait、notify 方法。

1、当线程 A 执行 wait 方法时,该线程会被挂起;

2、当线程 B 执行 notify 方法时,会唤醒一个被挂起的线程 A;

lock 对象、线程 A 和线程 B 三者是一种什么关系?根据上面的结论,可以想象一个场景:

1、lock 对象维护了一个等待队列 list;

2、线程 A 中执行 lock 的 wait 方法,把线程 A 保存到 list 中;

3、线程 B 中执行 lock 的 notify 方法,从等待队列中取出线程 A 继续执行;

当然了,Hotspot 实现不可能这么简单。

上述代码中,存在多个疑问:

1、进入 wait/notify 方法之前,为什么要获取 synchronized 锁?

2、线程 A 获取了 synchronized 锁,执行 wait 方法并挂起,线程 B 又如何再次获取锁?

为什么要使用 synchronized?

static void Sort(int [] array) {

// synchronize this operation so that some other thread can't // manipulate the array while we are sorting it. This assumes that other // threads also synchronize their accesses to the array. synchronized(array) { // now sort elements in array }

}

synchronized 代码块通过 javap 生成的字节码中包含 ** monitorenter ** 和 ** monitorexit **指令。

执行 monitorenter 指令可以获取对象的 monitor,而 lock.wait()方法通过调用 native 方法 wait(0)实现,其中接口注释中有这么一句:

The current thread must own this object's monitor.

表示线程执行 lock.wait()方法时,必须持有该 lock 对象的 monitor,如果 wait 方法在 synchronized 代码中执行,该线程很显然已经持有了 monitor。

代码执行过程分析

1、在多核环境下,线程 A 和 B 有可能同时执行 monitorenter 指令,并获取 lock 对象关联的 monitor,只有一个线程可以和 monitor 建立关联,假设线程 A 执行加锁成功;

2、线程 B 竞争加锁失败,进入等待队列进行等待;

3、线程 A 继续执行,当执行到 wait 方法时,会发生什么?wait 接口注释:

This method causes the current thread to place itself in the wait set for this object and then to relinquish any and all synchronization claims on this object.

wait 方法会将当前线程放入 wait set,等待被唤醒,并放弃 lock 对象上的所有同步声明,意味着线程 A 释放了锁,线程 B 可以重新执行加锁操作,不过又有一个疑问:在线程 A 的 wait 方法释放锁,到线程 B 获取锁,这期间发生了什么?线程 B 是如何知道线程 A 已经释放了锁?好迷茫....

4、线程 B 执行加锁操作成功,对于 notify 方法,JDK 注释:notify 方法会选择 wait set 中任意一个线程进行唤醒;

Wakes up a single thread that is waiting on this object's monitor. If any threads are waiting on this object, one of them is chosen to be awakened. The choice is arbitrary and occurs at the discretion of the implementation

notifyAll 方法的注释:notifyAll 方法会唤醒 monitor 的 wait set 中所有线程。

Wakes up all threads that are waiting on this object's monitor.

5、执行完 notify 方法,并不会立马唤醒等待线程,在 notify 方法后面加一段 sleep 代码就可以看到效果,如果线程 B 执行完 notify 方法之后 sleep 5s,在这段时间内,线程 B 依旧持有 monitor,线程 A 只能继续等待;

那么 wait set 的线程什么时候会被唤醒?

想要解答这些疑问, 需要分析 jvm 的相关实现,本文以 HotSpot 虚拟机 1.7 版本为例

什么是 monitor?

在 HotSpot 虚拟机中,monitor 采用 ObjectMonitor 实现。

每个线程都有两个 ObjectMonitor 对象列表,分别为 free 和 used 列表,如果当前 free 列表为空,线程将向全局 global list 请求分配 ObjectMonitor。

ObjectMonitor 对象中有两个队列:_WaitSet 和 _EntryList,用来保存 ObjectWaiter 对象列表;_owner 指向获得 ObjectMonitor 对象的线程。

**_WaitSet ** :处于 wait 状态的线程,会被加入到 wait set;

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

ObjectWaiter

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

wait 方法实现

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

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

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

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

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

notify 方法实现

lock.notify()方法最终通过 ObjectMonitor 的 void notify(TRAPS)实现:

1、如果当前_WaitSet 为空,即没有正在等待的线程,则直接返回;

2、通过 ObjectMonitor::DequeueWaiter 方法,获取_WaitSet 列表中的第一个 ObjectWaiter 节点,实现也很简单。

这里需要注意的是,在 jdk 的 notify 方法注释是随机唤醒一个线程,其实是第一个 ObjectWaiter 节点

3、根据不同的策略,将取出来的 ObjectWaiter 节点,加入到_EntryList 或则通过 Atomic::cmpxchg_ptr 指令进行自旋操作 cxq,具体代码实现有点长,这里就不贴了,有兴趣的同学可以看 objectMonitor::notify 方法;

notify ALL 方法实现

lock.notifyAll()方法最终通过 ObjectMonitor 的 void notifyAll(TRAPS)实现:

通过 for 循环取出_WaitSet 的 ObjectWaiter 节点,并根据不同策略,加入到_EntryList 或则进行自旋操作。

从 JVM 的方法实现中,可以发现:notify 和 notifyAll 并不会释放所占有的 ObjectMonitor 对象,其实真正释放 ObjectMonitor 对象的时间点是在执行 monitorexit 指令,一旦释放 ObjectMonitor 对象了,entry set 中 ObjectWaiter 节点所保存的线程就可以开始竞争 ObjectMonitor 对象进行加锁操作了。

  • Java

    Java 是一种可以撰写跨平台应用软件的面向对象的程序设计语言,是由 Sun Microsystems 公司于 1995 年 5 月推出的。Java 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3198 引用 • 8215 回帖 • 1 关注
  • JVM

    JVM(Java Virtual Machine)Java 虚拟机是一个微型操作系统,有自己的硬件构架体系,还有相应的指令系统。能够识别 Java 独特的 .class 文件(字节码),能够将这些文件中的信息读取出来,使得 Java 程序只需要生成 Java 虚拟机上的字节码后就能在不同操作系统平台上进行运行。

    180 引用 • 120 回帖
  • 12 引用 • 8 回帖

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...

推荐标签 标签

  • 旅游

    希望你我能在旅途中找到人生的下一站。

    95 引用 • 901 回帖
  • Gitea

    Gitea 是一个开源社区驱动的轻量级代码托管解决方案,后端采用 Go 编写,采用 MIT 许可证。

    5 引用 • 16 回帖 • 3 关注
  • Chrome

    Chrome 又称 Google 浏览器,是一个由谷歌公司开发的网页浏览器。该浏览器是基于其他开源软件所编写,包括 WebKit,目标是提升稳定性、速度和安全性,并创造出简单且有效率的使用者界面。

    63 引用 • 289 回帖 • 1 关注
  • 浅吟主题

    Jeffrey Chen 制作的思源笔记主题,项目仓库:https://github.com/TCOTC/Whisper

    1 引用 • 28 回帖 • 1 关注
  • SSL

    SSL(Secure Sockets Layer 安全套接层),及其继任者传输层安全(Transport Layer Security,TLS)是为网络通信提供安全及数据完整性的一种安全协议。TLS 与 SSL 在传输层对网络连接进行加密。

    70 引用 • 193 回帖 • 408 关注
  • H2

    H2 是一个开源的嵌入式数据库引擎,采用 Java 语言编写,不受平台的限制,同时 H2 提供了一个十分方便的 web 控制台用于操作和管理数据库内容。H2 还提供兼容模式,可以兼容一些主流的数据库,因此采用 H2 作为开发期的数据库非常方便。

    11 引用 • 54 回帖 • 668 关注
  • 微服务

    微服务架构是一种架构模式,它提倡将单一应用划分成一组小的服务。服务之间互相协调,互相配合,为用户提供最终价值。每个服务运行在独立的进程中。服务于服务之间才用轻量级的通信机制互相沟通。每个服务都围绕着具体业务构建,能够被独立的部署。

    96 引用 • 155 回帖 • 1 关注
  • 学习

    “梦想从学习开始,事业从实践起步” —— 习近平

    173 引用 • 518 回帖
  • Kubernetes

    Kubernetes 是 Google 开源的一个容器编排引擎,它支持自动化部署、大规模可伸缩、应用容器化管理。

    116 引用 • 54 回帖 • 1 关注
  • 博客

    记录并分享人生的经历。

    273 引用 • 2388 回帖
  • JWT

    JWT(JSON Web Token)是一种用于双方之间传递信息的简洁的、安全的表述性声明规范。JWT 作为一个开放的标准(RFC 7519),定义了一种简洁的,自包含的方法用于通信双方之间以 JSON 的形式安全的传递信息。

    20 引用 • 15 回帖 • 23 关注
  • FFmpeg

    FFmpeg 是一套可以用来记录、转换数字音频、视频,并能将其转化为流的开源计算机程序。

    23 引用 • 32 回帖
  • 游戏

    沉迷游戏伤身,强撸灰飞烟灭。

    181 引用 • 821 回帖 • 1 关注
  • JavaScript

    JavaScript 一种动态类型、弱类型、基于原型的直译式脚本语言,内置支持类型。它的解释器被称为 JavaScript 引擎,为浏览器的一部分,广泛用于客户端的脚本语言,最早是在 HTML 网页上使用,用来给 HTML 网页增加动态功能。

    730 引用 • 1280 回帖 • 4 关注
  • LeetCode

    LeetCode(力扣)是一个全球极客挚爱的高质量技术成长平台,想要学习和提升专业能力从这里开始,充足技术干货等你来啃,轻松拿下 Dream Offer!

    209 引用 • 72 回帖 • 2 关注
  • 安装

    你若安好,便是晴天。

    132 引用 • 1184 回帖
  • 域名

    域名(Domain Name),简称域名、网域,是由一串用点分隔的名字组成的 Internet 上某一台计算机或计算机组的名称,用于在数据传输时标识计算机的电子方位(有时也指地理位置)。

    44 引用 • 208 回帖 • 4 关注
  • CongSec

    本标签主要用于分享网络空间安全专业的学习笔记

    1 引用 • 1 回帖 • 29 关注
  • 运维

    互联网运维工作,以服务为中心,以稳定、安全、高效为三个基本点,确保公司的互联网业务能够 7×24 小时为用户提供高质量的服务。

    151 引用 • 257 回帖
  • 负能量

    上帝为你关上了一扇门,然后就去睡觉了....努力不一定能成功,但不努力一定很轻松 (° ー °〃)

    89 引用 • 1243 回帖 • 411 关注
  • TensorFlow

    TensorFlow 是一个采用数据流图(data flow graphs),用于数值计算的开源软件库。节点(Nodes)在图中表示数学操作,图中的线(edges)则表示在节点间相互联系的多维数据数组,即张量(tensor)。

    20 引用 • 19 回帖 • 3 关注
  • 锤子科技

    锤子科技(Smartisan)成立于 2012 年 5 月,是一家制造移动互联网终端设备的公司,公司的使命是用完美主义的工匠精神,打造用户体验一流的数码消费类产品(智能手机为主),改善人们的生活质量。

    4 引用 • 31 回帖 • 8 关注
  • ngrok

    ngrok 是一个反向代理,通过在公共的端点和本地运行的 Web 服务器之间建立一个安全的通道。

    7 引用 • 63 回帖 • 649 关注
  • Logseq

    Logseq 是一个隐私优先、开源的知识库工具。

    Logseq is a joyful, open-source outliner that works on top of local plain-text Markdown and Org-mode files. Use it to write, organize and share your thoughts, keep your to-do list, and build your own digital garden.

    7 引用 • 69 回帖
  • WiFiDog

    WiFiDog 是一套开源的无线热点认证管理工具,主要功能包括:位置相关的内容递送;用户认证和授权;集中式网络监控。

    1 引用 • 7 回帖 • 611 关注
  • Latke

    Latke 是一款以 JSON 为主的 Java Web 框架。

    71 引用 • 535 回帖 • 829 关注
  • PWL

    组织简介

    用爱发电 (Programming With Love) 是一个以开源精神为核心的民间开源爱好者技术组织,“用爱发电”象征开源与贡献精神,加入组织,代表你将遵守组织的“个人开源爱好者”的各项条款。申请加入:用爱发电组织邀请帖
    用爱发电组织官网:https://programmingwithlove.stackoverflow.wiki/

    用爱发电组织的核心驱动力:

    • 遵守开源守则,体现开源&贡献精神:以分享为目的,拒绝非法牟利。
    • 自我保护:使用适当的 License 保护自己的原创作品。
    • 尊重他人:不以各种理由、各种漏洞进行未经允许的抄袭、散播、洩露;以礼相待,尊重所有对社区做出贡献的开发者;通过他人的分享习得知识,要留下足迹,表示感谢。
    • 热爱编程、热爱学习:加入组织,热爱编程是首当其要的。我们欢迎热爱讨论、分享、提问的朋友,也同样欢迎默默成就的朋友。
    • 倾听:正确并恳切对待、处理问题与建议,及时修复开源项目的 Bug ,及时与反馈者沟通。不抬杠、不无视、不辱骂。
    • 平视:不诋毁、轻视、嘲讽其他开发者,主动提出建议、施以帮助,以和谐为本。只要他人肯努力,你也可能会被昔日小看的人所超越,所以请保持谦虚。
    • 乐观且活跃:你的努力决定了你的高度。不要放弃,多年后回头俯瞰,才会发现自己已经成就往日所仰望的水平。积极地将项目开源,帮助他人学习、改进,自己也会获得相应的提升、成就与成就感。
    1 引用 • 487 回帖 • 5 关注