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,这块空间很有自己的特点,前段时间公司这块的问题太多了,主要是因为升级了中间件所 ...
随机推荐
- 关于64位W7下怎么学习汇编语言的一些心得!
出处:http://tieba.baidu.com/p/2277546332 1.首先下载DOSBOX,它的作用就是让你在64位下使用32.16位的软件.如果不使用DOSBOX就会出现程序不兼容的对话 ...
- CentOS7搭建Pacemaker高可用集群(1)
Pacemaker是Red Hat High Availability Add-on的一部分.在RHEL上进行试用的最简单方法是从Scientific Linux 或CentOS存储库中进行安装 环境 ...
- tensorflow2.0学习笔记第一章第五节
1.5简单神经网络实现过程全览
- Windows学习Nodejs、Npm和VUE
前言 本文主要以开发的角度讲解Node.js,Npm和Vue. Node.js学习 什么是Node.js Node.js简单来说就是一个IISExpress,提供一个前端Html的独立运行环境. 安装 ...
- 大数据之Hudi + Kylin的准实时数仓实现
问题导读:1.数据库.数据仓库如何理解?2.数据湖有什么用途?解决什么问题?3.数据仓库的加载链路如何实现?4.Hudi新一代数据湖项目有什么优势? 在近期的 Apache Kylin × Apach ...
- Selenium上传文件方法大全
最好的方法:调js 其他方法: Python pywin32库,识别对话框句柄,进而操作 SendKeys库 autoIT,借助外力,我们去调用其生成的au3或exe文件. keybd_event,跟 ...
- cb15a_c++_vector容器的自增长_每次增加百分之50
cb15a_c++_vector容器的自增长_每次增加百分之50每次自动容量代销扩充,增加百分之50_for windows C++,vector是用数组做出来的->数组的缺点和优点优点:具有下 ...
- matplotlib 强化学习
matplotlib 强化学习 import matplotlib.pyplot as plt ...
v-bind的使用 v-bind: 是vue中,提供用于绑定属性的指令 例: <input type="button" value="按钮" title ...