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

“365篇原创计划”第十一篇。java

今天呢!灯塔君跟你们讲:node

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

最简单的东西,每每包含了最复杂的实现,由于须要为上层的存在提供一个稳定的基础,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方法。多线程

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

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

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

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

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

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

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

上述代码中,存在多个疑问:
一、进入wait/notify方法以前,为何要获取synchronized锁?

二、线程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。

代码执行过程分析

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

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

三、线程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已经释放了锁?好迷茫....

四、线程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.

五、执行完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);实现:

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

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

三、经过ObjectMonitor::exit方法释放当前的ObjectMonitor对象,这样其它竞争线程就能够获取该ObjectMonitor对象。

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

notify方法实现

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

一、若是当前_WaitSet为空,即没有正在等待的线程,则直接返回;

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

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

三、根据不一样的策略,将取出来的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对象进行加锁操做了。

365天干货不断微信搜索「猿灯塔」第一时间阅读,回复【资料】【面试】【简历】有我准备的一线大厂面试资料和简历模板
相关文章
相关标签/搜索