要理解并看懂下面讲解的Wait和notify/notifyAll实现原理,首先要弄明白synchronized的实现原理。synchronized的实现原理可以查看上篇文章。
wait,notify,notifyAll都是本地方法,所以首先要找到对应的Jdk的源码,在OpenJdk官网中可以找到映射关系如下:
找到对应的源码后,下面开始分析wait,notify,notifyAll的实现。
Wait实现
JVM_ENTRY(void, JVM_MonitorWait(JNIEnv* env, jobject handle, jlong ms))
JVMWrapper("JVM_MonitorWait");
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
JavaThreadInObjectWaitState jtiows(thread, ms != 0);
if (JvmtiExport::should_post_monitor_wait()) {
JvmtiExport::post_monitor_wait((JavaThread *)THREAD, (oop)obj(), ms);
// The current thread already owns the monitor and it has not yet
// been added to the wait queue so the current thread cannot be
// made the successor. This means that the JVMTI_EVENT_MONITOR_WAIT
// event handler cannot accidentally consume an unpark() meant for
// the ParkEvent associated with this ObjectMonitor.
}
ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END
这里调用的是 ObjectSynchronizer::wait 方法, ObjectSynchronizer::wait方法如下:
int ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
//如果使用偏向锁,首先撤销偏向。
//因为wait需要将线程放在ObjectMonitor的waitSet队列中,要wait必须要膨胀为重量级锁。
if (UseBiasedLocking) {
BiasedLocking::revoke_and_rebias(obj, false, THREAD);
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
if (millis < 0) {
TEVENT(wait - throw IAX);
THROW_MSG_0(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
}
//膨胀为重量级锁,如果已经是重量级锁, inflate方法会直接返回当前的ObjectMonitor
ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj());
DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
//调用ObjectMonitor的wait方法
monitor->wait(millis, true, THREAD);
// This dummy call is in place to get around dtrace bug 6254741. Once
// that's fixed we can uncomment the following line, remove the call
// and change this function back into a "void" func.
// DTRACE_MONITOR_PROBE(waited, monitor, obj(), THREAD);
return dtrace_waited_probe(monitor, obj, THREAD);
}
这里的操作,主要有三步。
- 判断如果当前锁对象使用偏向锁,首先撤销偏向。因为wait需要将线程放在ObjectMonitor的waitSet队列中,要wait必须要膨胀为重量级锁。关于偏向锁和重量级锁的膨胀,请看synchronized实现原理
- 膨胀为重量级锁,如果已经是重量级锁, inflate方法会直接返回当前的ObjectMonitor
- 调用ObjectMonitor的wait方法。
ObjectMonitor的wait方法内容如下:
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
Thread * const Self = THREAD;
assert(Self->is_Java_thread(), "Must be Java thread!");
JavaThread *jt = (JavaThread *)THREAD;
DeferredInitialize();
// Throw IMSX or IEX.
CHECK_OWNER();
EventJavaMonitorWait event;
// check for a pending interrupt
//检查是否被中断,中断了就就直接抛异常
if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
// post monitor waited event. Note that this is past-tense, we are done waiting.
if (JvmtiExport::should_post_monitor_waited()) {
// Note: 'false' parameter is passed here because the
// wait was not timed out due to thread interrupt.
JvmtiExport::post_monitor_waited(jt, this, false);
// In this short circuit of the monitor wait protocol, the
// current thread never drops ownership of the monitor and
// never gets added to the wait queue so the current thread
// cannot be made the successor. This means that the
// JVMTI_EVENT_MONITOR_WAITED event handler cannot accidentally
// consume an unpark() meant for the ParkEvent associated with
// this ObjectMonitor.
}
if (event.should_commit()) {
post_monitor_wait_event(&event, 0, millis, false);
}
TEVENT(Wait - Throw IEX);
THROW(vmSymbols::java_lang_InterruptedException());
return;
}
TEVENT(Wait);
assert(Self->_Stalled == 0, "invariant");
Self->_Stalled = intptr_t(this);
//设置当前线程等到的OObjectMonitor为当前的ObjectMonitor
jt->set_current_waiting_monitor(this);
// create a node to be put into the queue
// Critically, after we reset() the event but prior to park(), we must check
// for a pending interrupt.
//将当前线程封装成ObjectWaiter
ObjectWaiter node(Self);
node.TState = ObjectWaiter::TS_WAIT;
Self->_ParkEvent->reset();
OrderAccess::fence(); // ST into Event; membar ; LD interrupted-flag
// Enter the waiting queue, which is a circular doubly linked list in this case
// but it could be a priority queue or any data structure.
// _WaitSetLock protects the wait queue. Normally the wait queue is accessed only
// by the the owner of the monitor *except* in the case where park()
// returns because of a timeout of interrupt. Contention is exceptionally rare
// so we use a simple spin-lock instead of a heavier-weight blocking lock.
//对WaitSet队列加锁
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - add");
//将当前线程添加到WaitSet队列中
AddWaiter(&node);
//释放WaitSet队列锁
Thread::SpinRelease(&_WaitSetLock);
if ((SyncFlags & 4) == 0) {
_Responsible = NULL;
}
intptr_t save = _recursions; // record the old recursion count
_waiters++; // increment the number of waiters
_recursions = 0; // set the recursion level to be 1
//释放锁对象
exit(true, Self); // exit the monitor
guarantee(_owner != Self, "invariant");
// The thread is on the WaitSet list - now park() it.
// On MP systems it's conceivable that a brief spin before we park
// could be profitable.
//
// TODO-FIXME: change the following logic to a loop of the form
// while (!timeout && !interrupted && _notified == 0) park()
//上面已经释放了锁对象。所以下面将会将线程挂起。
//根据注释:这里是一个while循环,需要达到三种条件才会跳出循环,分别是:
//wait等待时间timeout到了。 中断。notify唤醒了。
int ret = OS_OK;
int WasNotified = 0;
{ // State transition wrappers
OSThread* osthread = Self->osthread();
OSThreadWaitState osts(osthread, true);
{
ThreadBlockInVM tbivm(jt);
// Thread is in thread_blocked state and oop access is unsafe.
jt->set_suspend_equivalent();
if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) {
// Intentionally empty
} else if (node._notified == 0) {
// 根据入参 millis 判断是否有条件的挂起。这里挂起后,就不会继续往下执行了。
//除非timeout到时了,或者中断,或者notify/notifyAll
if (millis <= 0) {
Self->_ParkEvent->park();
} else {
ret = Self->_ParkEvent->park(millis);
}
}
// were we externally suspended while we were waiting?
if (ExitSuspendEquivalent (jt)) {
// TODO-FIXME: add -- if succ == Self then succ = null.
jt->java_suspend_self();
}
} // Exit thread safepoint: transition _thread_blocked -> _thread_in_vm
// Node may be on the WaitSet, the EntryList (or cxq), or in transition
// from the WaitSet to the EntryList.
// See if we need to remove Node from the WaitSet.
// We use double-checked locking to avoid grabbing _WaitSetLock
// if the thread is not on the wait queue.
//
// Note that we don't need a fence before the fetch of TState.
// In the worst case we'll fetch a old-stale value of TS_WAIT previously
// written by the is thread. (perhaps the fetch might even be satisfied
// by a look-aside into the processor's own store buffer, although given
// the length of the code path between the prior ST and this load that's
// highly unlikely). If the following LD fetches a stale TS_WAIT value
// then we'll acquire the lock and then re-fetch a fresh TState value.
// That is, we fail toward safety.
//执行到这儿,说明park的线程,已经被唤醒了,可能是timeout到时了,或者中断,或者notify/notifyAll
//首先要将其从WaitSet队列中移除。
if (node.TState == ObjectWaiter::TS_WAIT) {
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - unlink");
if (node.TState == ObjectWaiter::TS_WAIT) {
DequeueSpecificWaiter(&node); // unlink from WaitSet
assert(node._notified == 0, "invariant");
node.TState = ObjectWaiter::TS_RUN;
}
Thread::SpinRelease(&_WaitSetLock);
}
// The thread is now either on off-list (TS_RUN),
// on the EntryList (TS_ENTER), or on the cxq (TS_CXQ).
// The Node's TState variable is stable from the perspective of this thread.
// No other threads will asynchronously modify TState.
guarantee(node.TState != ObjectWaiter::TS_WAIT, "invariant");
OrderAccess::loadload();
if (_succ == Self) _succ = NULL;
//WasNotified用于判断,当前节点是否是通过notify唤醒的
WasNotified = node._notified;
// Reentry phase -- reacquire the monitor.
// re-enter contended monitor after object.wait().
// retain OBJECT_WAIT state until re-enter successfully completes
// Thread state is thread_in_vm and oop access is again safe,
// although the raw address of the object may have changed.
// (Don't cache naked oops over safepoints, of course).
// post monitor waited event. Note that this is past-tense, we are done waiting.
if (JvmtiExport::should_post_monitor_waited()) {
JvmtiExport::post_monitor_waited(jt, this, ret == OS_TIMEOUT);
if (node._notified != 0 && _succ == Self) {
// In this part of the monitor wait-notify-reenter protocol it
// is possible (and normal) for another thread to do a fastpath
// monitor enter-exit while this thread is still trying to get
// to the reenter portion of the protocol.
//
// The ObjectMonitor was notified and the current thread is
// the successor which also means that an unpark() has already
// been done. The JVMTI_EVENT_MONITOR_WAITED event handler can
// consume the unpark() that was done when the successor was
// set because the same ParkEvent is shared between Java
// monitors and JVM/TI RawMonitors (for now).
//
// We redo the unpark() to ensure forward progress, i.e., we
// don't want all pending threads hanging (parked) with none
// entering the unlocked monitor.
node._event->unpark();
}
}
if (event.should_commit()) {
post_monitor_wait_event(&event, node._notifier_tid, millis, ret == OS_TIMEOUT);
}
OrderAccess::fence();
assert(Self->_Stalled != 0, "invariant");
Self->_Stalled = 0;
assert(_owner != Self, "invariant");
//要继续执行,必须重新获取锁才可以,这里就是重新获取锁的逻辑。
ObjectWaiter::TStates v = node.TState;
if (v == ObjectWaiter::TS_RUN) {
enter(Self);
} else {
guarantee(v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant");
ReenterI(Self, &node);
node.wait_reenter_end(this);
}
// Self has reacquired the lock.
// Lifecycle - the node representing Self must not appear on any queues.
// Node is about to go out-of-scope, but even if it were immortal we wouldn't
// want residual elements associated with this thread left on any lists.
guarantee(node.TState == ObjectWaiter::TS_RUN, "invariant");
assert(_owner == Self, "invariant");
assert(_succ != Self, "invariant");
} // OSThreadWaitState()
//执行到这儿,说明线程被唤醒,并且重新获取了锁。
//设置当前线程wait的Monitor为NULL
jt->set_current_waiting_monitor(NULL);
guarantee(_recursions == 0, "invariant");
_recursions = save; // restore the old recursion count
_waiters--; // decrement the number of waiters
// Verify a few postconditions
assert(_owner == Self, "invariant");
assert(_succ != Self, "invariant");
assert(((oop)(object()))->mark() == markOopDesc::encode(this), "invariant");
if (SyncFlags & 32) {
OrderAccess::fence();
}
// check if the notification happened
//是否是notify唤醒的,如果不是,判断如果是中断,需要抛出中断异常
if (!WasNotified) {
// no, it could be timeout or Thread.interrupt() or both
// check for interrupt event, otherwise it is timeout
if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
TEVENT(Wait - throw IEX from epilog);
THROW(vmSymbols::java_lang_InterruptedException());
}
}
}
wait过程中,将ObjectWaiter添加到WaitSet队列的逻辑如下:
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
assert(node != NULL, "should not add NULL node");
assert(node->_prev == NULL, "node already in list");
assert(node->_next == NULL, "node already in list");
// put node at end of queue (circular doubly linked list)
if (_WaitSet == NULL) {
_WaitSet = node;
node->_prev = node;
node->_next = node;
} else {
ObjectWaiter* head = _WaitSet;
ObjectWaiter* tail = head->_prev;
assert(tail->_next == head, "invariant check");
tail->_next = node;
head->_prev = node;
node->_next = head;
node->_prev = tail;
}
}
可以看到,WaitSet是一个双向循环队列,每次都会将新的元素放在队列的末尾。
整个Wait过程,内容较多,但是整体逻辑还是很清晰的
- 将线程封装成ObjectWaiter。
- 添加到WaitSet队列,WaitSet是一个双向循环队列,将其添加到了队列的末尾
- 调用exit方法释放锁,exit是退出synchronized的逻辑,详细可看synchronized实现原理
- 将线程挂起,这里挂起根据入参millis进行了有条件的挂起(是否到时间自动唤醒)。挂起之后,线程就不再执行了,必须等待当前线程被唤醒才会继续执行。线程被唤醒有三种情况:到时间自动唤醒,中断唤醒,notify/notifyAll唤醒。
- 从WaitSet中移除
- 重新获取锁。
- 判断是否是中断唤醒,如果是中断唤醒,则抛出中断异常。
具体的流程图如下:
Notify实现
Notify的逻辑相对还是很简单的。
首先是执行JVM_MonitorNotify:
JVM_ENTRY(void, JVM_MonitorNotify(JNIEnv* env, jobject handle))
JVMWrapper("JVM_MonitorNotify");
Handle obj(THREAD, JNIHandles::resolve_non_null(handle));
ObjectSynchronizer::notify(obj, CHECK);
JVM_END
这里直接调用 ObjectSynchronizer::notify:
void ObjectSynchronizer::notify(Handle obj, TRAPS) {
//如果使用偏向,先撤销偏向
if (UseBiasedLocking) {
BiasedLocking::revoke_and_rebias(obj, false, THREAD);
assert(!obj->mark()->has_bias_pattern(), "biases should be revoked by now");
}
//判断如果是轻量级锁,直接返回
markOop mark = obj->mark();
if (mark->has_locker() && THREAD->is_lock_owned((address)mark->locker())) {
return;
}
//获取重量级锁的ObjectMonitor并执行其notify方法
ObjectSynchronizer::inflate(THREAD, obj())->notify(THREAD);
}
这里,首先判断,如果使用了偏向锁,则先撤销偏向。继续判断,如果当前是轻量级锁,直接返回。最后获取重量级锁的ObjectMonitor并执行其notify方法.
ObjectMonitor::notify逻辑如下:
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();
//如果_WaitSet == NULL,则没有需要唤醒的线程,直接返回
if (_WaitSet == NULL) {
TEVENT(Empty-Notify);
return;
}
DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
//执行真正的唤醒
INotify(THREAD);
OM_PERFDATA_OP(Notifications, inc(1));
}
这里先判断了WaitSet是否为空,如果是NULL,说明没有需要被唤醒的线程,直接返回。如果不为NULL,则执行真正的唤醒:
void ObjectMonitor::INotify(Thread * Self) {
const int policy = Knob_MoveNotifyee;
Thread::SpinAcquire(&_WaitSetLock, "WaitSet - notify");
ObjectWaiter * iterator = DequeueWaiter();
if (iterator != NULL) {
TEVENT(Notify1 - Transfer);
guarantee(iterator->TState == ObjectWaiter::TS_WAIT, "invariant");
guarantee(iterator->_notified == 0, "invariant");
// Disposition - what might we do with iterator ?
// a. add it directly to the EntryList - either tail (policy == 1)
// or head (policy == 0).
// b. push it onto the front of the _cxq (policy == 2).
// For now we use (b).
if (policy != 4) {
iterator->TState = ObjectWaiter::TS_ENTER;
}
//设置线程是通过notify唤醒
iterator->_notified = 1;
iterator->_notifier_tid = Self->osthread()->thread_id();
ObjectWaiter * list = _EntryList;
if (list != NULL) {
assert(list->_prev == NULL, "invariant");
assert(list->TState == ObjectWaiter::TS_ENTER, "invariant");
assert(list != iterator, "invariant");
}
//下面根据不同的策略进行唤醒。
if (policy == 0) { // prepend to EntryList
//添加到EntryList队列的队首
if (list == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
list->_prev = iterator;
iterator->_next = list;
iterator->_prev = NULL;
_EntryList = iterator;
}
} else if (policy == 1) { // append to EntryList
//添加到EntryList队列的队尾
if (list == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
// CONSIDER: finding the tail currently requires a linear-time walk of
// the EntryList. We can make tail access constant-time by converting to
// a CDLL instead of using our current DLL.
ObjectWaiter * tail;
for (tail = list; tail->_next != NULL; tail = tail->_next) /* empty */;
assert(tail != NULL && tail->_next == NULL, "invariant");
tail->_next = iterator;
iterator->_prev = tail;
iterator->_next = NULL;
}
} else if (policy == 2) { // prepend to cxq
//添加到CXQ队列的队首
if (list == NULL) {
iterator->_next = iterator->_prev = NULL;
_EntryList = iterator;
} else {
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter * front = _cxq;
iterator->_next = front;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, front) == front) {
break;
}
}
}
} else if (policy == 3) { // append to cxq
//添加CXQ队列的队尾
iterator->TState = ObjectWaiter::TS_CXQ;
for (;;) {
ObjectWaiter * tail = _cxq;
if (tail == NULL) {
iterator->_next = NULL;
if (Atomic::cmpxchg_ptr(iterator, &_cxq, NULL) == NULL) {
break;
}
} else {
while (tail->_next != NULL) tail = tail->_next;
tail->_next = iterator;
iterator->_prev = tail;
iterator->_next = NULL;
break;
}
}
} else {
ParkEvent * ev = iterator->_event;
iterator->TState = ObjectWaiter::TS_RUN;
OrderAccess::fence();
ev->unpark();
}
// _WaitSetLock protects the wait queue, not the EntryList. We could
// move the add-to-EntryList operation, above, outside the critical section
// protected by _WaitSetLock. In practice that's not useful. With the
// exception of wait() timeouts and interrupts the monitor owner
// is the only thread that grabs _WaitSetLock. There's almost no contention
// on _WaitSetLock so it's not profitable to reduce the length of the
// critical section.
if (policy < 4) {
iterator->wait_reenter_begin(this);
}
}
Thread::SpinRelease(&_WaitSetLock);
}
唤醒过程中,从WaitSet中取出要唤醒的ObjectWaiter的逻辑如下:
inline ObjectWaiter* ObjectMonitor::DequeueWaiter() {
// dequeue the very first waiter
ObjectWaiter* waiter = _WaitSet;
if (waiter) {
DequeueSpecificWaiter(waiter);
}
return waiter;
}
//队列队首的线程从队列中去掉。_WaitSet指向下一个线程。
inline void ObjectMonitor::DequeueSpecificWaiter(ObjectWaiter* node) {
assert(node != NULL, "should not dequeue NULL node");
assert(node->_prev != NULL, "node already removed from list");
assert(node->_next != NULL, "node already removed from list");
// when the waiter has woken up because of interrupt,
// timeout or other spurious wake-up, dequeue the
// waiter from waiting list
ObjectWaiter* next = node->_next;
if (next == node) {
//说明只有一个Wait线程
assert(node->_prev == node, "invariant check");
_WaitSet = NULL;
} else {
ObjectWaiter* prev = node->_prev;
assert(prev->_next == node, "invariant check");
assert(next->_prev == node, "invariant check");
next->_prev = prev;
prev->_next = next;
if (_WaitSet == node) {
_WaitSet = next;
}
}
node->_next = NULL;
node->_prev = NULL;
}
可以看出,是从队列的队首开始取出第一个元素,取出后,WaitSet指向下一个元素。
这里唤醒的逻辑很简单,
- 首先设置ObjectWaiter的notifyed状态为1,这里是为了在线程别唤醒后判断是否是通过notify正常唤醒的,前面讲notify的时候有讲到。
- 根据不同的唤醒策略进行不同的操作:
policy == 0 : 取出WaitSet的队首元素,添加到EntryList队列的队首。
policy == 1 : 取出WaitSet的队首元素,添加到EntryList队列的队尾
policy == 2 : 取出WaitSet的队首元素,添加到CXQ队列的队首
policy == 3 : 取出WaitSet的队首元素,添加CXQ队列的队尾
JVM末尾的唤醒策略是policy == 2 ,将WaitSet的队首元素,添加到CXQ队列的队首
到这里Notify唤醒就结束了,可以看到,这里notify其实并没有真正唤醒线程,只是根据不同的策略,将WaitSet中队首的元素放在了EntryList和CXQ队列中,那什么时候执行真正的唤醒呢?在执行Notify的线程退出Synchronized执行Exit的时候,会根据不同的策略从CXQ和EntryList队列中唤醒线程。徐姐需要去看synchronized实现原理
唤醒流程如下:
NotifyAll实现:
void ObjectMonitor::notifyAll(TRAPS) {
CHECK_OWNER();
if (_WaitSet == NULL) {
TEVENT(Empty-NotifyAll);
return;
}
DTRACE_MONITOR_PROBE(notifyAll, this, object(), THREAD);
int tally = 0;
while (_WaitSet != NULL) {
tally++;
INotify(THREAD);
}
OM_PERFDATA_OP(Notifications, inc(tally));
}
看懂了Notify,那NotifyAll就更简单了,只是遍历WaitSet,对每个等到线程都执行了Notify唤醒。