Java Yield Wait Park Sleep

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 前面我们已经介绍了 Java Thread 的实现以及用来进行线程等待的 Parker 和 ParkEvent 的实现,本篇文章中我们介绍一下 Parker 和 ParkEvent 的使用,即 Yield、Wait、Park、Sleep。

引言

前面我们已经介绍了 Java Thread 的实现以及用来进行线程等待的 Parker 和 ParkEvent 的实现,本篇文章中我们介绍一下 Parker 和 ParkEvent 的使用,即 Yield、Wait、Park、Sleep。所有关于 Java 并发的文章均收录于<Java并发系列文章>

Yield Wait Park Sleep

和 Java 并发打交道,可能经常会有让当前线程先等一等,等待某一事件发生再继续向下执行的情况。而 Thread 的 Yield Wait Park Sleep 都能造成线程的等待,并让出 CPU 执行权,那么它们都有什么区别呢?

Yield

先从最简单的 Yield 说起, 它的作用是通知操作系统的调度器,让出当前线程的执行权, 等调度器再次选中该线程执行时,再继续往下执行。

/**
 * A hint to the scheduler that the current thread is willing to yield
 * its current use of a processor. The scheduler is free to ignore this
 * hint.
 *
 * <p> Yield is a heuristic attempt to improve relative progression
 * between threads that would otherwise over-utilise a CPU. Its use
 * should be combined with detailed profiling and benchmarking to
 * ensure that it actually has the desired effect.
 *
 * <p> It is rarely appropriate to use this method. It may be useful
 * for debugging or testing purposes, where it may help to reproduce
 * bugs due to race conditions. It may also be useful when designing
 * concurrency control constructs such as the ones in the
 * {@link java.util.concurrent.locks} package.
 */
public static native void yield();

yield 是一个 native 的函数,这里我们以 JDK8 的 hotspot 为例看一下它的实现。

JVM_ENTRY(void, JVM_Yield(JNIEnv *env, jclass threadClass))
  JVMWrapper("JVM_Yield");
  if (os::dont_yield()) return;
#ifndef USDT2
  HS_DTRACE_PROBE0(hotspot, thread__yield);
#else /* USDT2 */
  HOTSPOT_THREAD_YIELD();
#endif /* USDT2 */
  // When ConvertYieldToSleep is off (default), this matches the classic VM use of yield.
  // Critical for similar threading behaviour
  if (ConvertYieldToSleep) {
    os::sleep(thread, MinSleepInterval, false);
  } else {
    os::yield();
  }
JVM_END

从上述代码可见最终默认会执行到 os::yield 函数, 它最终调用了 sched_yield ,这是一个系统调用,最终会执行到当前线程所处调度器类的 yield_task 函数,最后调用调度函数 schedule

void os::yield() {
  sched_yield();
}

SYSCALL_DEFINE0(sched_yield)
{
  do_sched_yield();
  return 0;
}

static void do_sched_yield(void)
{
  // ...
  current->sched_class->yield_task(rq);
  // ...
  schedule();
}

接下来我们以完全公平调度器类(cfs_sched_class)为例来介绍更下层的处理流程。因为一般的 Java 线程都是由它进行调度管理。在完全公平调度器类中 yield_task 对应的函数是 yield_task_fair ,其中主要是更新当前线程的虚拟运行时间,这个虚拟运行时间是由实际运行时间和线程的优先级加权计算的,然后将当前线程标记成了skip。这里我们不展开介绍了,具体的内容可以参考我的 Linux系列文章

static void yield_task_fair(struct rq *rq)
{
    // ...
    clear_buddies(cfs_rq, se);
    if (curr->policy != SCHED_BATCH) {
        update_rq_clock(rq);
        update_curr(cfs_rq); // 更新当前任务的vruntime等信息
        rq_clock_skip_update(rq);
    }
    set_skip_buddy(se); // 设置当前任务为cfs_rq->skip
}

接下来进行一次__schedule()的调用,通俗地讲,就是从当前CPU的调度队列中取出一个任务执行,并将前一个任务放回队列中去。

对于普通任务来说,对应的实现函数在pick_next_entity中,这个函数从cfs_rq的调度队列(通过红黑树管理)取出下一个调度实体。

pick_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *curr)
{
    if (!left || (curr && entity_before(curr, left)))
        left = curr; // 如果没有标记skip的话,当前任务还有可能继续执行

    se = left;
    // ...
    if (cfs_rq->skip == se) {
        struct sched_entity *second;
        if (se == curr) {
            second = __pick_first_entity(cfs_rq); // 如果标记为skip了,从队列中取一个出来
        }
    // ...
    if (second && wakeup_preempt_entity(second, left) < 1)
        se = second;
    }
    // ...
    return se;
}

static struct task_struct * pick_next_task_fair(...)
{
  // ...
  if (prev != p) { // 表示需要任务的切换了
    //...
    put_prev_entity(cfs_rq, pse); // 将当前任务再添加到红黑树中
    set_next_entity(cfs_rq, se); // 从红黑树中移除一个节点,并且设置为当前任务
  }
  // ...
}

此时当前任务其实被标记为skip了,所以即使当前任务的虚拟运行时间比调度队列(红黑树排序)中虚拟运行时间最小的节点还要小,也会返回调度队列中的下一个线程,作为下一次要执行的任务。也就是说,只要红黑树不是空的,当前线程就会让出CPU。然后,当前任务还要添加到队列里面去等待下一次调度。

Sleep

Sleep 和 Yield 类似,都是 JNI 方法,从文档中我们可以看出它和 Yield 类似也会让出执行时间,除此之外,它还能睡眠一段时间,值得一提的是,在调用 Sleep 时并不会释放 Monitor 锁。

/**
 * Causes the currently executing thread to sleep (temporarily cease
 * execution) for the specified number of milliseconds, subject to
 * the precision and accuracy of system timers and schedulers. The thread
 * does not lose ownership of any monitors.
 *
 * @param  millis
 *         the length of time to sleep in milliseconds
 *
 * @throws  IllegalArgumentException
 *          if the value of {@code millis} is negative
 *
 * @throws  InterruptedException
 *          if any thread has interrupted the current thread. The
 *          <i>interrupted status</i> of the current thread is
 *          cleared when this exception is thrown.
 */
public static native void sleep(long millis) throws InterruptedException;

接下来我们看一下 Sleep 的下层实现。

JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis))
  // 检查是否已经被中断
  // ...
  if (millis == 0) {
    if (ConvertSleepToYield) { // 默认是false
      os::yield();
    } else {
      ThreadState old_state = thread->osthread()->get_state();
      thread->osthread()->set_state(SLEEPING);
      os::sleep(thread, MinSleepInterval, false); // 小睡 1 ms
      thread->osthread()->set_state(old_state);
    }
  } else {
    ThreadState old_state = thread->osthread()->get_state();
    thread->osthread()->set_state(SLEEPING);
    if (os::sleep(thread, millis, true) == OS_INTRPT) {
      // 处理中断
    }
    thread->osthread()->set_state(old_state);
  }
  // ...
JVM_END

这里除了一些处理中断的逻辑外,就是调用 os::sleep 接口了,在 Linux 平台下,它的实现如下:

int os::sleep(Thread* thread, jlong millis, bool interruptible) {
  assert(thread == Thread::current(),  "thread consistency check");

  ParkEvent * const slp = thread->_SleepEvent ;
  slp->reset() ;
  // 加入内存屏障
  OrderAccess::fence() ;

  if (interruptible) { // 根据是否可以中断,进行不同的处理
    jlong prevtime = javaTimeNanos();

    for (;;) {
      if (os::is_interrupted(thread, true)) { // 可以中断的情况下,如果当前线程被中断了,直接返回,外层函数负责抛出 InterruptedException
        return OS_INTRPT;
      }

      jlong newtime = javaTimeNanos();

      if (newtime - prevtime < 0) {
        // time moving backwards, should only happen if no monotonic clock
        // not a guarantee() because JVM should not abort on kernel/glibc bugs
        assert(!Linux::supports_monotonic_clock(), "time moving backwards");
      } else {
        millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC; // 计算剩余睡眠时间
      }

      if(millis <= 0) { // 如果剩余睡眠时间小于 0,直接返回
        return OS_OK;
      }

      prevtime = newtime;

      {
        assert(thread->is_Java_thread(), "sanity check");
        JavaThread *jt = (JavaThread *) thread;
        ThreadBlockInVM tbivm(jt);
        OSThreadWaitState osts(jt->osthread(), false /* not Object.wait() */);

        jt->set_suspend_equivalent();
        // cleared by handle_special_suspend_equivalent_condition() or
        // java_suspend_self() via check_and_wait_while_suspended()

        slp->park(millis); // 利用 ParkEvent 的 park 函数进行休眠

        // were we externally suspended while we were waiting?
        jt->check_and_wait_while_suspended();
      }
    }
  } else {
    OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
    jlong prevtime = javaTimeNanos();

    for (;;) { // 无论中断与否,都循环进行休眠,直到休眠时间达到要求
      // It'd be nice to avoid the back-to-back javaTimeNanos() calls on
      // the 1st iteration ...
      jlong newtime = javaTimeNanos();

      if (newtime - prevtime < 0) {
        // time moving backwards, should only happen if no monotonic clock
        // not a guarantee() because JVM should not abort on kernel/glibc bugs
        assert(!Linux::supports_monotonic_clock(), "time moving backwards");
      } else {
        millis -= (newtime - prevtime) / NANOSECS_PER_MILLISEC;
      }

      if(millis <= 0) break ;

      prevtime = newtime;
      slp->park(millis); // 利用 ParkEvent 的 park 函数进行休眠
    }
    return OS_OK ;
  }
}

Sleep 的实现使用到了 thread->_SleepEvent ,也就是 ParkEvent 的park函数。

Wait

/**
 * Causes the current thread to wait until either another thread invokes the
 * {@link java.lang.Object#notify()} method or the
 * {@link java.lang.Object#notifyAll()} method for this object, or a
 * specified amount of time has elapsed.
 * <p>
 * The current thread must own this object's monitor.
 * <p>
 * This method causes the current thread (call it <var>T</var>) to
 * place itself in the wait set for this object and then to relinquish
 * any and all synchronization claims on this object. Thread <var>T</var>
 * becomes disabled for thread scheduling purposes and lies dormant
 * until one of four things happens:
 * <ul>
 * <li>Some other thread invokes the {@code notify} method for this
 * object and thread <var>T</var> happens to be arbitrarily chosen as
 * the thread to be awakened.
 * <li>Some other thread invokes the {@code notifyAll} method for this
 * object.
 * <li>Some other thread {@linkplain Thread#interrupt() interrupts}
 * thread <var>T</var>.
 * <li>The specified amount of real time has elapsed, more or less.  If
 * {@code timeout} is zero, however, then real time is not taken into
 * consideration and the thread simply waits until notified.
 * </ul>
 * The thread <var>T</var> is then removed from the wait set for this
 * object and re-enabled for thread scheduling. It then competes in the
 * usual manner with other threads for the right to synchronize on the
 * object; once it has gained control of the object, all its
 * synchronization claims on the object are restored to the status quo
 * ante - that is, to the situation as of the time that the {@code wait}
 * method was invoked. Thread <var>T</var> then returns from the
 * invocation of the {@code wait} method. Thus, on return from the
 * {@code wait} method, the synchronization state of the object and of
 * thread {@code T} is exactly as it was when the {@code wait} method
 * was invoked.
 * <p>
 * A thread can also wake up without being notified, interrupted, or
 * timing out, a so-called <i>spurious wakeup</i>.  While this will rarely
 * occur in practice, applications must guard against it by testing for
 * the condition that should have caused the thread to be awakened, and
 * continuing to wait if the condition is not satisfied.  In other words,
 * waits should always occur in loops, like this one:
 * <pre>
 *     synchronized (obj) {
 *         while (<condition does not hold>)
 *             obj.wait(timeout);
 *         ... // Perform action appropriate to condition
 *     }
 * </pre>
 *
 * <p>If the current thread is {@linkplain java.lang.Thread#interrupt()
 * interrupted} by any thread before or while it is waiting, then an
 * {@code InterruptedException} is thrown.  This exception is not
 * thrown until the lock status of this object has been restored as
 * described above.
 *
 * <p>
 * Note that the {@code wait} method, as it places the current thread
 * into the wait set for this object, unlocks only this object; any
 * other objects on which the current thread may be synchronized remain
 * locked while the thread waits.
 * <p>
 * This method should only be called by a thread that is the owner
 * of this object's monitor. See the {@code notify} method for a
 * description of the ways in which a thread can become the owner of
 * a monitor.
 *
 * @param      timeout   the maximum time to wait in milliseconds.
 * @throws  IllegalArgumentException      if the value of timeout is
 *               negative.
 * @throws  IllegalMonitorStateException  if the current thread is not
 *               the owner of the object's monitor.
 * @throws  InterruptedException if any thread interrupted the
 *             current thread before or while the current thread
 *             was waiting for a notification.  The <i>interrupted
 *             status</i> of the current thread is cleared when
 *             this exception is thrown.
 * @see        java.lang.Object#notify()
 * @see        java.lang.Object#notifyAll()
 */
public final native void wait(long timeout) throws InterruptedException;

在使用 wait 的时候,我们需要先获取该对象的 monitor 锁,然后再调用 wait 函数,而且在调用之后,当前线程不仅会进入睡眠,同时也会自动释放 monitor 锁,是不是感觉和 mutex 和 condition 的使用过程一样?接下来,我们看一下 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);
    }
    ObjectSynchronizer::wait(obj, ms, CHECK);
JVM_END

void ObjectSynchronizer::wait(Handle obj, jlong millis, TRAPS) {
    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(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative");
    }
    ObjectMonitor* monitor = ObjectSynchronizer::inflate(THREAD, obj()); // 膨胀为重量级锁
    DTRACE_MONITOR_WAIT_PROBE(monitor, obj(), THREAD, millis);
    monitor->wait(millis, true, THREAD); // 调用wait
    dtrace_waited_probe(monitor, obj, THREAD);
}

从上述代码中,我们可以看到 wait 函数直接调用了 ObjectSynchronizer 也就是 monitor 的 wait 函数,该函数中先将当前 monitor 锁升级到了重量级锁,因为无论是偏向锁还是轻量级锁都是不具备线程等待能力的。升级完成后,调用了 monitor 的wait函数。

void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
    Thread * const Self = THREAD ;
    // ...
    if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) {
     // ...
     THROW(vmSymbols::java_lang_InterruptedException()); // 处理中断
     return ;
    }
    // ...
    // 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 node(Self);
    node.TState = ObjectWaiter::TS_WAIT ;
    Self->_ParkEvent->reset() ;
    OrderAccess::fence();          // ST into Event; membar ; LD interrupted-flag
    // ...
    Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;
    AddWaiter (&node) ; // 1. 添加到ObjectMonitor的等待队列_WaitSet中
    Thread::SpinRelease (&_WaitSetLock) ;
    exit (true, Self) ; // 2. 释放java的monitor锁(也就是monitorexit)
    // ...
    // As soon as the ObjectMonitor's ownership is dropped in the exit()
    // call above, another thread can enter() the ObjectMonitor, do the
    // notify(), and exit() the ObjectMonitor. If the other thread's
    // exit() call chooses this thread as the successor and the unpark()
    // call happens to occur while this thread is posting a
    // MONITOR_CONTENDED_EXIT event, then we run the risk of the event
    // handler using RawMonitors and consuming the unpark().
    //
    // To avoid the problem, we re-post the event. This does no harm
    // even if the original unpark() was not consumed because we are the
    // chosen successor for this monitor.
    if (node._notified != 0 && _succ == Self) {
     node._event->unpark();
    }
    // ...
       if (interruptible &&
           (Thread::is_interrupted(THREAD, false) ||
            HAS_PENDING_EXCEPTION)) {
           // Intentionally empty
       } else if (node._notified == 0) {
         if (millis <= 0) {
            Self->_ParkEvent->park ();
         } else {
            ret = Self->_ParkEvent->park (millis) ; // 3. 等待,和Thread::sleep一样的
         }
       }
    //...
    // 重新获得 monitor 锁,这时候可能有3种情况,一种是当前线程状态已经是运行中,一种是当前线程在锁等待队列中,最后一种是当前线程在 cxq 队列(ContentionList)
    // 具体处于哪种状态跟 notify 的实现策略有关,我们稍后就介绍
    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);
    }
    //...
}

monitor 的wait函数主要做了以下几件事:

  1. 检查是否被中断(后续也有很多中断的检查,这里不再赘述),一旦发现被中断就抛出 InterruptedException
  2. 通过自旋锁保护,将当前线程添加到等待队列中
  3. 释放 Monitor 锁
  4. 检查释放 Monitor 锁之后,进入等待之前,是否有其他线程调用了notify,虽然这里进行了检查,但是后续的等待的过程并没有使用到锁,所以还是会出现先notify后进入等待的情况,这和 ParkEvent->park 内部的实现方式不太一样,ParkEvent->park 是通过mutex和condition配合使用来达到“Unpark->Park(不进入等待,直接苏醒)”的效果
  5. 通过 Self->_ParkEvent->park 进入等待状态,这一点和 Thread::sleep的实现类似
  6. 最后,从等待中恢复后,当前线程可能处于 TS_RUN(wait线程抢占其他等待锁的线程),TS_ENTER(wait线程进入EntryList 等待队列),TS_CXQ (wait线程进入ContentionList 等待队列)这三种状态中的任何一个,具体处于哪一个,要看 notify 的实现策略

接下来我们看一下 notify 的实现方式:

// Consider:
// If the lock is cool (cxq == null && succ == null) and we're on an MP system
// then instead of transferring a thread from the WaitSet to the EntryList
// we might just dequeue a thread from the WaitSet and directly unpark() it.

void ObjectMonitor::notify(TRAPS) {
    CHECK_OWNER();
    // 检查是否有等待的线程
    if (_WaitSet == NULL) {
     TEVENT (Empty-Notify) ;
     return ;
    }
    DTRACE_MONITOR_PROBE(notify, this, object(), THREAD);
    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") ;
     if (Policy != 4) {
        iterator->TState = ObjectWaiter::TS_ENTER ;
     }
     iterator->_notified = 1 ;
     Thread * Self = THREAD;
     iterator->_notifier_tid = Self->osthread()->thread_id();
     ObjectWaiter * List = _EntryList ;
     //...
     if (Policy == 0) {       // prepend to 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
         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) ;
            assert (Tail != NULL && Tail->_next == NULL, "invariant") ;
            Tail->_next = iterator ;
            iterator->_prev = Tail ;
            iterator->_next = NULL ;
        }
     } else
     if (Policy == 2) {      // prepend to cxq
         // prepend to 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
        iterator->TState = ObjectWaiter::TS_CXQ ;
        for (;;) {
            ObjectWaiter * Tail ;
            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() ;
     }
     if (Policy < 4) {
       iterator->wait_reenter_begin(this);
     }
     // _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.
    }
    Thread::SpinRelease (&_WaitSetLock) ;
    if (iterator != NULL) {
      OM_PERFDATA_OP(Notifications, inc(1));
    }
}

notify 的主要工作如下:

  1. 检查是否有等待线程,如果没有就什么都不做,期间用到了等待队列的自旋锁
  2. 根据策略 Knob_MoveNotifyee 进行不同的处理,默认值是 2

    1. 策略0:将等待队列中第一个线程,插入到 EntryList 的队头
    2. 策略1:将等待队列中第一个线程,插入到 EntryList 的队尾
    3. 策略2:将等待队列中第一个线程,插入到 cxq(ContentionList) 的队头
    4. 策略3:将等待队列中第一个线程,插入到 cxq(ContentionList) 的队尾
    5. 策略4:将等待线程的状态置为 RUNING,直接参与锁的竞争,策略 4 和策略 0 的区别在于,策略4可以直接和 monitor 锁的候选人进行竞争
  3. 释放等待队列的自旋锁

这里我们回顾一下 Monitor 锁的各个队列关系,大家应该就清楚了。一个 monitor 对象包括这么几个关键字段:ContentionList,EntryList ,WaitSet,owner。其中 cxq(ContentionList) ,EntryList ,WaitSet 都是由 ObjectWaiter 的链表结构,owner 指向持有锁的线程。

monitor-lock

当一个线程尝试获得锁时,如果该锁已经被占用,则会将该线程封装成一个 ObjectWaiter 对象插入到 ContentionList 的队列,然后暂停当前线程, ContentionList 类似于一个 CopyOnWriteList 的角色,来减少 EntryList 的竞争。当持有锁的线程释放锁前,会将 ContentionList 中的所有元素移动到 EntryList 中去,并唤醒 EntryList 的队首线程。这个选中的线程叫做 Heir presumptive 即假定继承人,就是图中的 Ready Thread,假定继承人被唤醒后会尝试获得锁,但 synchronized 是非公平的,所以假定继承人不一定能获得锁,它其实会和当前正准备获得锁的线程进行争夺,看完上述 notify 的实现,我们知道如果当前处于策略4的话,被唤醒的线程也会参与竞争。

这里我们就不细讲 NotifyAll 的实现了,它的实现就是在 Notify 的基础上加了一个循环,循环处理所有等待队列中的线程。

Park

LockSupport 的 park 函数提供了让当前进程等待的机制,它和 wait 的不同时,如果我们先执行了 notify 再执行 wait 的话,wait 会被一直阻塞,而先 unpark 再 park 的话,park 会立即返回。但是这并不是说 park 比 wait 要好,因为 wait notify 要配合 synchronized 使用,你完全可以在自己的代码中维护一个令牌,通过 synchronized 保护,如果当前令牌有效就不进行 wait,否则再wait,其实也能达到和 park unpark 一样的效果。另外,和wait方法不同,执行park进入休眠后并不会释放持有的锁。park方法不会抛出InterruptedException,但是它也会响应中断(标记中断标志位)。当外部线程对阻塞线程调用interrupt方法时,park阻塞的线程也会立刻返回。

介绍完 park 和 wait 的区别,让我们深入到 LockSupport::park 的实现中来吧。

// LockSupport.java
/**

* Disables the current thread for thread scheduling purposes unless the
* permit is available.

*

* <p>If the permit is available then it is consumed and the call
* returns immediately; otherwise the current thread becomes disabled
* for thread scheduling purposes and lies dormant until one of three
* things happens:

*

* <ul>

*

* <li>Some other thread invokes {@link #unpark unpark} with the
* current thread as the target; or

*

* <li>Some other thread {@linkplain Thread#interrupt interrupts}
* the current thread; or

*

* <li>The call spuriously (that is, for no reason) returns.
* </ul>

*

* <p>This method does <em>not</em> report which of these caused the
* method to return. Callers should re-check the conditions which caused
* the thread to park in the first place. Callers may also determine,
* for example, the interrupt status of the thread upon return.

*/
public static void park() {
    UNSAFE.park(false, 0L);
}

// UNSAFE.java
public native void park(boolean var1, long var2);

在 UNSAFE 的 park 函数下层调用的就是我们前面介绍的 parker。

UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
  // ...
  thread->parker()->park(isAbsolute != 0, time);
  // ...
UNSAFE_END

之前我们已经详细介绍过 Parker 的实现了,这里就不再赘述了。

总结

java-wait-methods-different

文章说明

更多有价值的文章均收录于贝贝猫的文章目录

stun

版权声明: 本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!

创作声明: 本文基于下列所有参考内容进行创作,其中可能涉及复制、修改或者转换,图片均来自网络,如有侵权请联系我,我会第一时间进行删除。

参考内容

[1] linux 2.6 互斥锁的实现-源码分析
[2] 深入解析条件变量(condition variables)
[3] Linux下Condition Vairable和Mutext合用的小细节
[4] 从ReentrantLock的实现看AQS的原理及应用
[5] 不可不说的Java“锁”事
[6] 从源码层面解析yield、sleep、wait、park
[7] LockSupport中的park与unpark原理
[8] Thread.sleep、Object.wait、LockSupport.park 区别
[9] 从AQS到futex-二-JVM的Thread和Parker
[10] Java的LockSupport.park()实现分析
[11] JVM源码分析之Object.wait/notify实现
[12] Java线程源码解析之interrupt
[14] Java CAS 原理剖析
[15] 源码解析 Java 的 compareAndSwapObject 到底比较的是什么
[16] 《Java并发编程的艺术》
[17] 《实战 Java 高并发程序设计》
[18] volatile关键字深入学习
[19] 为什么Netty的FastThreadLocal速度快
[20] 线程池ThreadPoolExecutor实现原理
[21] 深入理解Java线程池:ThreadPoolExecutor
[22] ConcurrentHashMap 详解一
[23] ConcurrentHashMap 详解二
[24] JUC中Atomic class之lazySet的一点疑惑
[25] The JSR-133 Cookbook for Compiler Writers
[26] 就是要你懂Java中volatile关键字实现原理

相关实践学习
部署Stable Diffusion玩转AI绘画(GPU云服务器)
本实验通过在ECS上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。
相关文章
|
26天前
|
安全 Java
Java中WAIT和NOTIFY方法调用时机的深层解析
在Java多线程编程中,`wait()`和`notify()`方法的正确使用对于线程间的协调至关重要。这两个方法必须在同步块或同步方法中调用,这一规定的深层原因是什么呢?本文将深入探讨这一机制。
35 5
|
26天前
|
安全 Java 开发者
Java中WAIT和NOTIFY方法必须在同步块中调用的原因
在Java多线程编程中,`wait()`和`notify()`方法是实现线程间协作的关键。这两个方法必须在同步块或同步方法中调用,这一要求背后有着深刻的原因。本文将深入探讨为什么`wait()`和`notify()`方法必须在同步块中调用,以及这一机制如何确保线程安全和避免死锁。
37 4
|
1月前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
60 9
|
1月前
|
Java
JAVA多线程通信:为何wait()与notify()如此重要?
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是实现线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件满足时被唤醒,从而确保数据一致性和同步。相比其他通信方式,如忙等待,这些方法更高效灵活。 示例代码展示了如何在生产者-消费者模型中使用这些方法实现线程间的协调和同步。
38 3
|
2月前
|
安全 Java
Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧
【10月更文挑战第20天】Java多线程通信新解:本文通过生产者-消费者模型案例,深入解析wait()、notify()、notifyAll()方法的实用技巧,包括避免在循环外调用wait()、优先使用notifyAll()、确保线程安全及处理InterruptedException等,帮助读者更好地掌握这些方法的应用。
24 1
|
2月前
|
Java
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅
在Java多线程编程中,`wait()`和`notify()`方法的相遇如同一场奇妙的邂逅。它们用于线程间通信,使线程能够协作完成任务。通过这些方法,生产者和消费者线程可以高效地管理共享资源,确保程序的有序运行。正确使用这些方法需要遵循同步规则,避免虚假唤醒等问题。示例代码展示了如何在生产者-消费者模型中使用`wait()`和`notify()`。
32 1
|
2月前
|
安全 Java 开发者
Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用
本文深入解析了Java多线程中的`wait()`、`notify()`和`notifyAll()`方法,探讨了它们在实现线程间通信和同步中的关键作用。通过示例代码展示了如何正确使用这些方法,并分享了最佳实践,帮助开发者避免常见陷阱,提高多线程程序的稳定性和效率。
51 1
|
2月前
|
Java
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。
在Java多线程编程中,`wait()` 和 `notify()/notifyAll()` 方法是线程间通信的核心机制。它们通过基于锁的方式,使线程在条件不满足时进入休眠状态,并在条件成立时被唤醒,从而有效解决数据一致性和同步问题。本文通过对比其他通信机制,展示了 `wait()` 和 `notify()` 的优势,并通过生产者-消费者模型的示例代码,详细说明了其使用方法和重要性。
31 1
|
4月前
|
Java 调度
java 中sleep 注意点
java 中sleep 注意点
|
4月前
|
Java
Java 中 sleep 和 wait 之间的区别?
【8月更文挑战第21天】
332 0