《JVM源码分析之Object.wait/notify实现》
《Java 中的 Monitor 机制》
使用
public class TestWait {
private static Object waitObject = new Object();
public static void main(String[] args) throws Exception {
new Thread(
() -> {
System.out.println("A");
synchronized (waitObject) {
System.out.println("A1");
try {
waitObject.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("A2");
}
System.out.println("A3");
}
).start();
new Thread(
() -> {
System.out.println("C");
synchronized (waitObject) {
System.out.println("C1");
waitObject.notifyAll();
System.out.println("C2");
}
System.out.println("C3");
}
).start();
}
}
运行结果:
A
A1
C
C1
C2
C3
A2
A3
方法
方法 | 作用 | 注意点 |
---|---|---|
wait | 线程进入等待池,释放锁;超过时间,通过notify/notifyAll 或者线程中断, 停止等待 | 调用之前必须获取synchronized 锁,否则抛出IllegalMonitorStateException |
notify | 从等待池中,随机唤醒一个线程(其实是等待池的第一个) | 调用之前必须获取synchronized 锁,否则抛出IllegalMonitorStateException |
notifyAll | 唤醒等待池中的所有线程 | 调用之前必须获取synchronized 锁,否则抛出IllegalMonitorStateException |
wait
最终通过ObjectMonitor
的void wait(jlong millis, bool interruptable, TRAPS)
实现的:
- 将当前线程封装成ObjectWaiter对象node
- 通过ObjectMonitor::AddWaiter方法将node添加到_WaitSet列表中;
- 通过
ObjectMonitor::exit
方法释放当前的ObjectMonitor
对象,这样其它竞争线程就可以获取该ObjectMonitor
对象 - 最终底层的park方法会挂起线程
疑问解答
-
对象调用
wait/notify/notifyAll
之前,为什么要先对对象加synchronized
锁?
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。 -
其他线程是如何知道执行wait的线程释放了锁?
-
为什么调用notify/notifyAll时并不会立刻唤醒等待线程?
对2,3的讲解:
在HotSpot虚拟机(JVM概念的实现)中,monitor采用ObjectMonitor
实现。
每个对象都有一个ObjectMonitor
对象,ObjectMonitor
对象随对象一同创建并销毁。ObjectMonitor
对象中有两个队列:_WaitSet
和_EntryList
,用来保存ObjectWaiter
对象列表;_owner指向获得ObjectMonitor对象的线程。
上图步骤讲解: -
线程执行到临界区(锁住的代码),如果可以获得锁,则进入The Owner中;如果不能获得锁,则阻塞在Entry Set中。
-
Entry Set中的线程竞争锁,获得锁的线程进入The Owner中。
-
如果The Owner中的线程执行了
wait
方法,则会释放锁和CPU,同时该线程进入Wait Set中;接着执行步骤2. -
The Owner中的线程执行了notify/notifyAll, 根据不同的策略,将从Wait Set中取出来的ObjectWaiter节点,加入到Entry Set中或则通过自旋
5.The Owner中的线程执行完临界区后会释放锁,销毁线程