Wait与notify/notifyAll源码分析

13 篇文章 2 订阅

要理解并看懂下面讲解的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);
}

这里的操作,主要有三步。

  1. 判断如果当前锁对象使用偏向锁,首先撤销偏向。因为wait需要将线程放在ObjectMonitor的waitSet队列中,要wait必须要膨胀为重量级锁。关于偏向锁和重量级锁的膨胀,请看synchronized实现原理
  2. 膨胀为重量级锁,如果已经是重量级锁, inflate方法会直接返回当前的ObjectMonitor
  3. 调用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过程,内容较多,但是整体逻辑还是很清晰的

  1. 将线程封装成ObjectWaiter。
  2. 添加到WaitSet队列,WaitSet是一个双向循环队列,将其添加到了队列的末尾
  3. 调用exit方法释放锁,exit是退出synchronized的逻辑,详细可看synchronized实现原理
  4. 将线程挂起,这里挂起根据入参millis进行了有条件的挂起(是否到时间自动唤醒)。挂起之后,线程就不再执行了,必须等待当前线程被唤醒才会继续执行。线程被唤醒有三种情况:到时间自动唤醒,中断唤醒,notify/notifyAll唤醒。
  5. 从WaitSet中移除
  6. 重新获取锁。
  7. 判断是否是中断唤醒,如果是中断唤醒,则抛出中断异常。

具体的流程图如下:
在这里插入图片描述

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指向下一个元素。

这里唤醒的逻辑很简单,

  1. 首先设置ObjectWaiter的notifyed状态为1,这里是为了在线程别唤醒后判断是否是通过notify正常唤醒的,前面讲notify的时候有讲到。
  2. 根据不同的唤醒策略进行不同的操作:
    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唤醒。

  • 4
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值