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

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

最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提供一个稳定的基础,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 技术具有卓越的通用性、高效性、平台移植性和安全性。

    3167 引用 • 8207 回帖 • 1 关注
  • JVM

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

    180 引用 • 120 回帖 • 1 关注
  • 11 引用 • 8 回帖

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • Spark

    Spark 是 UC Berkeley AMP lab 所开源的类 Hadoop MapReduce 的通用并行框架。Spark 拥有 Hadoop MapReduce 所具有的优点;但不同于 MapReduce 的是 Job 中间输出结果可以保存在内存中,从而不再需要读写 HDFS,因此 Spark 能更好地适用于数据挖掘与机器学习等需要迭代的 MapReduce 的算法。

    74 引用 • 46 回帖 • 550 关注
  • FreeMarker

    FreeMarker 是一款好用且功能强大的 Java 模版引擎。

    23 引用 • 20 回帖 • 427 关注
  • PostgreSQL

    PostgreSQL 是一款功能强大的企业级数据库系统,在 BSD 开源许可证下发布。

    21 引用 • 22 回帖
  • Webswing

    Webswing 是一个能将任何 Swing 应用通过纯 HTML5 运行在浏览器中的 Web 服务器,详细介绍请看 将 Java Swing 应用变成 Web 应用

    1 引用 • 15 回帖 • 633 关注
  • Sphinx

    Sphinx 是一个基于 SQL 的全文检索引擎,可以结合 MySQL、PostgreSQL 做全文搜索,它可以提供比数据库本身更专业的搜索功能,使得应用程序更容易实现专业化的全文检索。

    1 引用 • 178 关注
  • Linux

    Linux 是一套免费使用和自由传播的类 Unix 操作系统,是一个基于 POSIX 和 Unix 的多用户、多任务、支持多线程和多 CPU 的操作系统。它能运行主要的 Unix 工具软件、应用程序和网络协议,并支持 32 位和 64 位硬件。Linux 继承了 Unix 以网络为核心的设计思想,是一个性能稳定的多用户网络操作系统。

    915 引用 • 931 回帖
  • 快应用

    快应用 是基于手机硬件平台的新型应用形态;标准是由主流手机厂商组成的快应用联盟联合制定;快应用标准的诞生将在研发接口、能力接入、开发者服务等层面建设标准平台;以平台化的生态模式对个人开发者和企业开发者全品类开放。

    15 引用 • 127 回帖
  • 一些有用的避坑指南。

    69 引用 • 93 回帖
  • JRebel

    JRebel 是一款 Java 虚拟机插件,它使得 Java 程序员能在不进行重部署的情况下,即时看到代码的改变对一个应用程序带来的影响。

    26 引用 • 78 回帖 • 618 关注
  • jsoup

    jsoup 是一款 Java 的 HTML 解析器,可直接解析某个 URL 地址、HTML 文本内容。它提供了一套非常省力的 API,可通过 DOM,CSS 以及类似于 jQuery 的操作方法来取出和操作数据。

    6 引用 • 1 回帖 • 457 关注
  • API

    应用程序编程接口(Application Programming Interface)是一些预先定义的函数,目的是提供应用程序与开发人员基于某软件或硬件得以访问一组例程的能力,而又无需访问源码,或理解内部工作机制的细节。

    76 引用 • 421 回帖
  • Google

    Google(Google Inc.,NASDAQ:GOOG)是一家美国上市公司(公有股份公司),于 1998 年 9 月 7 日以私有股份公司的形式创立,设计并管理一个互联网搜索引擎。Google 公司的总部称作“Googleplex”,它位于加利福尼亚山景城。Google 目前被公认为是全球规模最大的搜索引擎,它提供了简单易用的免费服务。不作恶(Don't be evil)是谷歌公司的一项非正式的公司口号。

    49 引用 • 192 回帖 • 1 关注
  • IDEA

    IDEA 全称 IntelliJ IDEA,是一款 Java 语言开发的集成环境,在业界被公认为最好的 Java 开发工具之一。IDEA 是 JetBrains 公司的产品,这家公司总部位于捷克共和国的首都布拉格,开发人员以严谨著称的东欧程序员为主。

    180 引用 • 400 回帖 • 1 关注
  • BND

    BND(Baidu Netdisk Downloader)是一款图形界面的百度网盘不限速下载器,支持 Windows、Linux 和 Mac,详细介绍请看这里

    107 引用 • 1281 回帖 • 21 关注
  • AngularJS

    AngularJS 诞生于 2009 年,由 Misko Hevery 等人创建,后为 Google 所收购。是一款优秀的前端 JS 框架,已经被用于 Google 的多款产品当中。AngularJS 有着诸多特性,最为核心的是:MVC、模块化、自动化双向数据绑定、语义化标签、依赖注入等。2.0 版本后已经改名为 Angular。

    12 引用 • 50 回帖 • 422 关注
  • JSON

    JSON (JavaScript Object Notation)是一种轻量级的数据交换格式。易于人类阅读和编写。同时也易于机器解析和生成。

    51 引用 • 190 回帖
  • Flutter

    Flutter 是谷歌的移动 UI 框架,可以快速在 iOS 和 Android 上构建高质量的原生用户界面。 Flutter 可以与现有的代码一起工作,它正在被越来越多的开发者和组织使用,并且 Flutter 是完全免费、开源的。

    39 引用 • 92 回帖 • 7 关注
  • 又拍云

    又拍云是国内领先的 CDN 服务提供商,国家工信部认证通过的“可信云”,乌云众测平台认证的“安全云”,为移动时代的创业者提供新一代的 CDN 加速服务。

    21 引用 • 37 回帖 • 512 关注
  • 笔记

    好记性不如烂笔头。

    303 引用 • 777 回帖
  • Docker

    Docker 是一个开源的应用容器引擎,让开发者可以打包他们的应用以及依赖包到一个可移植的容器中,然后发布到任何流行的操作系统上。容器完全使用沙箱机制,几乎没有性能开销,可以很容易地在机器和数据中心中运行。

    476 引用 • 899 回帖
  • ngrok

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

    7 引用 • 63 回帖 • 596 关注
  • Electron

    Electron 基于 Chromium 和 Node.js,让你可以使用 HTML、CSS 和 JavaScript 构建应用。它是一个由 GitHub 及众多贡献者组成的活跃社区共同维护的开源项目,兼容 Mac、Windows 和 Linux,它构建的应用可在这三个操作系统上面运行。

    15 引用 • 136 回帖 • 8 关注
  • Maven

    Maven 是基于项目对象模型(POM)、通过一小段描述信息来管理项目的构建、报告和文档的软件项目管理工具。

    185 引用 • 318 回帖 • 348 关注
  • Java

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

    3167 引用 • 8207 回帖 • 1 关注
  • LeetCode

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

    209 引用 • 72 回帖
  • Facebook

    Facebook 是一个联系朋友的社交工具。大家可以通过它和朋友、同事、同学以及周围的人保持互动交流,分享无限上传的图片,发布链接和视频,更可以增进对朋友的了解。

    4 引用 • 15 回帖 • 454 关注
  • BAE

    百度应用引擎(Baidu App Engine)提供了 PHP、Java、Python 的执行环境,以及云存储、消息服务、云数据库等全面的云服务。它可以让开发者实现自动地部署和管理应用,并且提供动态扩容和负载均衡的运行环境,让开发者不用考虑高成本的运维工作,只需专注于业务逻辑,大大降低了开发者学习和迁移的成本。

    19 引用 • 75 回帖 • 619 关注