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

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

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

    3168 引用 • 8207 回帖
  • JVM

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

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

相关帖子

欢迎来到这里!

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

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

推荐标签 标签

  • GitLab

    GitLab 是利用 Ruby 一个开源的版本管理系统,实现一个自托管的 Git 项目仓库,可通过 Web 界面操作公开或私有项目。

    46 引用 • 72 回帖
  • Thymeleaf

    Thymeleaf 是一款用于渲染 XML/XHTML/HTML5 内容的模板引擎。类似 Velocity、 FreeMarker 等,它也可以轻易的与 Spring 等 Web 框架进行集成作为 Web 应用的模板引擎。与其它模板引擎相比,Thymeleaf 最大的特点是能够直接在浏览器中打开并正确显示模板页面,而不需要启动整个 Web 应用。

    11 引用 • 19 回帖 • 319 关注
  • Scala

    Scala 是一门多范式的编程语言,集成面向对象编程和函数式编程的各种特性。

    13 引用 • 11 回帖 • 108 关注
  • 倾城之链
    23 引用 • 66 回帖 • 102 关注
  • 资讯

    资讯是用户因为及时地获得它并利用它而能够在相对短的时间内给自己带来价值的信息,资讯有时效性和地域性。

    53 引用 • 85 回帖
  • HBase

    HBase 是一个分布式的、面向列的开源数据库,该技术来源于 Fay Chang 所撰写的 Google 论文 “Bigtable:一个结构化数据的分布式存储系统”。就像 Bigtable 利用了 Google 文件系统所提供的分布式数据存储一样,HBase 在 Hadoop 之上提供了类似于 Bigtable 的能力。

    17 引用 • 6 回帖 • 45 关注
  • Swagger

    Swagger 是一款非常流行的 API 开发工具,它遵循 OpenAPI Specification(这是一种通用的、和编程语言无关的 API 描述规范)。Swagger 贯穿整个 API 生命周期,如 API 的设计、编写文档、测试和部署。

    26 引用 • 35 回帖 • 13 关注
  • FreeMarker

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

    23 引用 • 20 回帖 • 428 关注
  • CAP

    CAP 指的是在一个分布式系统中, Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。

    11 引用 • 5 回帖 • 565 关注
  • 书籍

    宋真宗赵恒曾经说过:“书中自有黄金屋,书中自有颜如玉。”

    76 引用 • 390 回帖
  • React

    React 是 Facebook 开源的一个用于构建 UI 的 JavaScript 库。

    192 引用 • 291 回帖 • 443 关注
  • 房星科技

    房星网,我们不和没有钱的程序员谈理想,我们要让程序员又有理想又有钱。我们有雄厚的房地产行业线下资源,遍布昆明全城的 100 家门店、四千地产经纪人是我们坚实的后盾。

    6 引用 • 141 回帖 • 559 关注
  • VirtualBox

    VirtualBox 是一款开源虚拟机软件,最早由德国 Innotek 公司开发,由 Sun Microsystems 公司出品的软件,使用 Qt 编写,在 Sun 被 Oracle 收购后正式更名成 Oracle VM VirtualBox。

    10 引用 • 2 回帖 • 5 关注
  • IBM

    IBM(国际商业机器公司)或万国商业机器公司,简称 IBM(International Business Machines Corporation),总公司在纽约州阿蒙克市。1911 年托马斯·沃森创立于美国,是全球最大的信息技术和业务解决方案公司,拥有全球雇员 30 多万人,业务遍及 160 多个国家和地区。

    16 引用 • 53 回帖 • 123 关注
  • MySQL

    MySQL 是一个关系型数据库管理系统,由瑞典 MySQL AB 公司开发,目前属于 Oracle 公司。MySQL 是最流行的关系型数据库管理系统之一。

    675 引用 • 535 回帖
  • etcd

    etcd 是一个分布式、高可用的 key-value 数据存储,专门用于在分布式系统中保存关键数据。

    5 引用 • 26 回帖 • 492 关注
  • gRpc
    10 引用 • 8 回帖 • 54 关注
  • Hexo

    Hexo 是一款快速、简洁且高效的博客框架,使用 Node.js 编写。

    21 引用 • 140 回帖 • 28 关注
  • OpenStack

    OpenStack 是一个云操作系统,通过数据中心可控制大型的计算、存储、网络等资源池。所有的管理通过前端界面管理员就可以完成,同样也可以通过 Web 接口让最终用户部署资源。

    10 引用 • 6 关注
  • Tomcat

    Tomcat 最早是由 Sun Microsystems 开发的一个 Servlet 容器,在 1999 年被捐献给 ASF(Apache Software Foundation),隶属于 Jakarta 项目,现在已经独立为一个顶级项目。Tomcat 主要实现了 JavaEE 中的 Servlet、JSP 规范,同时也提供 HTTP 服务,是市场上非常流行的 Java Web 容器。

    162 引用 • 529 回帖 • 3 关注
  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 23 关注
  • C

    C 语言是一门通用计算机编程语言,应用广泛。C 语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。

    83 引用 • 165 回帖 • 44 关注
  • TextBundle

    TextBundle 文件格式旨在应用程序之间交换 Markdown 或 Fountain 之类的纯文本文件时,提供更无缝的用户体验。

    1 引用 • 2 回帖 • 47 关注
  • Pipe

    Pipe 是一款小而美的开源博客平台。Pipe 有着非常活跃的社区,可将文章作为帖子推送到社区,来自社区的回帖将作为博客评论进行联动(具体细节请浏览 B3log 构思 - 分布式社区网络)。

    这是一种全新的网络社区体验,让热爱记录和分享的你不再感到孤单!

    131 引用 • 1114 回帖 • 151 关注
  • 阿里云

    阿里云是阿里巴巴集团旗下公司,是全球领先的云计算及人工智能科技公司。提供云服务器、云数据库、云安全等云计算服务,以及大数据、人工智能服务、精准定制基于场景的行业解决方案。

    89 引用 • 345 回帖
  • SEO

    发布对别人有帮助的原创内容是最好的 SEO 方式。

    35 引用 • 200 回帖 • 23 关注
  • Spark

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

    74 引用 • 46 回帖 • 549 关注