JVM源码分析之Object.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锁?
为什么要使用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 **指令。
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实现。
_EntryList:处于等待锁block状态的线程,会被加入到entry set;
ObjectWaiter
wait方法实现
lock.wait()方法最终通过ObjectMonitor的void wait(jlong millis, bool interruptable, TRAPS);实现:
1、将当前线程封装成ObjectWaiter对象node;
ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;3、通过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就可以获取该ObjectMonitor对象。
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方法;
notifyAll方法实现
lock.notifyAll()方法最终通过ObjectMonitor的void notifyAll(TRAPS)实现:
通过for循环取出_WaitSet的ObjectWaiter节点,并根据不同策略,加入到_EntryList或则进行自旋操作。
从JVM的方法实现中,可以发现:notify和notifyAll并不会释放所占有的ObjectMonitor对象,其实真正释放ObjectMonitor对象的时间点是在执行monitorexit指令,一旦释放ObjectMonitor对象了,entry set中ObjectWaiter节点所保存的线程就可以开始竞争ObjectMonitor对象进行加锁操作了。
JVM源码分析之Object.wait/notify实现的更多相关文章
- JVM源码分析之Object.wait/notify(All)完全解读
概述 本文其实一直都想写,因为各种原因一直拖着没写,直到开公众号的第一天,有朋友再次问到这个问题,这次让我静心下来准备写下这篇文章,本文有些东西是我自己的理解,比如为什么JDK一开始要这么设计,初衷是 ...
- JVM源码分析之Object.wait/notify实现(转载)
最简单的东西,往往包含了最复杂的实现,因为需要为上层的存在提供一个稳定的基础,Object作为java中所有对象的基类,其存在的价值不言而喻,其中wait和notify方法的实现多线程协作提供了保证. ...
- JVM源码分析之synchronized实现
“365篇原创计划”第十二篇. 今天呢!灯塔君跟大家讲: JVM源码分析之synchronized实现 java内部锁synchronized的出现,为多线程的并发执行提供了一个稳定的 ...
- JVM源码分析之堆外内存完全解读
JVM源码分析之堆外内存完全解读 寒泉子 2016-01-15 17:26:16 浏览6837 评论0 阿里技术协会 摘要: 概述 广义的堆外内存 说到堆外内存,那大家肯定想到堆内内存,这也是我们 ...
- JVM源码分析之警惕存在内存泄漏风险的FinalReference(增强版)
概述 JAVA对象引用体系除了强引用之外,出于对性能.可扩展性等方面考虑还特地实现了四种其他引用:SoftReference.WeakReference.PhantomReference.FinalR ...
- JVM源码分析-类加载场景实例分析
A类调用B类的静态方法,除了加载B类,但是B类的一个未被调用的方法间接使用到的C类却也被加载了,这个有意思的场景来自一个提问:方法中使用的类型为何在未调用时尝试加载?. 场景如下: public cl ...
- JVM源码分析之SystemGC完全解读
JVM源码分析之SystemGC完全解读 概述 JVM的GC一般情况下是JVM本身根据一定的条件触发的,不过我们还是可以做一些人为的触发,比如通过jvmti做强制GC,通过System.gc触发,还可 ...
- JVM源码分析之一个Java进程究竟能创建多少线程
JVM源码分析之一个Java进程究竟能创建多少线程 原创: 寒泉子 你假笨 2016-12-06 概述 虽然这篇文章的标题打着JVM源码分析的旗号,不过本文不仅仅从JVM源码角度来分析,更多的来自于L ...
- JVM源码分析之Metaspace解密
概述 metaspace,顾名思义,元数据空间,专门用来存元数据的,它是jdk8里特有的数据结构用来替代perm,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级了中间件所 ...
随机推荐
- Java实现 LeetCode 102 二叉树的层次遍历
102. 二叉树的层次遍历 给定一个二叉树,返回其按层次遍历的节点值. (即逐层地,从左到右访问所有节点). 例如: 给定二叉树: [3,9,20,null,null,15,7], 3 / \ 9 2 ...
- Java实现 蓝桥杯 算法提高 快速排序
试题 算法提高 快速排序 资源限制 时间限制:1.0s 内存限制:256.0MB 问题描述 用递归来实现快速排序(quick sort)算法.快速排序算法的基本思路是:假设要对一个数组a进行排序,且a ...
- Java实现第八届蓝桥杯最大公共子串
最大公共子串 最大公共子串长度问题就是: 求两个串的所有子串中能够匹配上的最大长度是多少. 比如:"abcdkkk" 和 "baabcdadabc", 可以找到 ...
- 使用Java将阿拉伯数字转换为中文数字(适配小数转换)
Java数字转换工具类 简介 该工具类可以将整数.小数.负数转换为中文的数字,如: 0 --> 零 1 --> 一 2.1 --> 二点一 -2.1 --> 负二点一 具体代码 ...
- Java对象实例化的过程
1.先为对象分配空间,并按属性类型默认初始化 ps:八种基本数据类型,按照默认方式初始化,其他数据类型默认为null 2.父类属性的初始化(包括代码块,和属性按照代码顺序进行初始化) 3.父类构造函数 ...
- NetAnalyzer笔记 之 十四 NetAnalyzer 6.0 的使用方法 -- 3.协议分析与统计
数据分析 完成了数据的抓取,那么接下来就是NetAnalyzer的第二个重点部分了,协议分析作为整个软件的核心之一,在最新的NetAnalyzer中已经得到了巨大的提升.NetAnalyzer中协议分 ...
- MySql轻松入门系列——第二站 使用visual studio 对mysql进行源码级调试
一:背景 1. 讲故事 上一篇说了mysql的架构图,很多同学反馈说不过瘾,毕竟还是听我讲故事,那这篇就来说一说怎么利用visual studio 对 mysql进行源码级调试,毕竟源码面前,不谈隐私 ...
- 内存管理,goto的使用,内存的申请和释放,mmap,ioremap
1.内存管理 (将物理内存映射到内核空间(3G~4G)并使用) 深入内核: 伙伴系统 1.1基本概念 1)linux内核管理内存是以物理内存页为单位 一个物理内存页通常为4KB ...
- AntD框架的upload组件上传图片时使用customRequest方法自定义上传行为
本次做后台管理系统,采用的是 AntD 框架.涉及到图片的上传,用的是AntD的 upload 组件. 我在上一篇文章<AntD框架的upload组件上传图片时使用customRequest方法 ...
- 50道Java集合经典面试题(收藏版)
前言 来了来了,50道Java集合面试题也来啦~ 已经上传github: https://github.com/whx123/JavaHome 1. Arraylist与LinkedList区别 可以 ...