LockSupport及HotSpot層Parker::park/unpark分析

1.官方文檔

Basic thread blocking primitives for creating locks and other 
synchronization classes.

This class associates, with each thread that uses it, a permit (in the 
sense of the Semaphore class). A call to park will return immediately 
if the permit is available, consuming it in the process; otherwise it may 
block. A call to unpark makes the permit available, if it was not 
already available. (Unlike with Semaphores though, permits do not 
accumulate. There is at most one.)

Methods park and unpark provide efficient means of blocking and 
unblocking threads that do not encounter the problems that cause the 
deprecated methods Thread.suspend and Thread.resume to be 
unusable for such purposes: Races between one thread invoking park 
and another thread trying to unpark it will preserve liveness, due to 
the permit. Additionally, park will return if the caller's thread was 
interrupted, and timeout versions are supported. The park method 
may also return at any other time, for "no reason", so in general must 
be invoked within a loop that rechecks conditions upon return. In this 
sense park serves as an optimization of a "busy wait" that does not 
waste as much time spinning, but must be paired with an unpark to be 
effective.

The three forms of park each also support a blocker object parameter. 
This object is recorded while the thread is blocked to permit 
monitoring and diagnostic tools to identify the reasons that threads 
are blocked. (Such tools may access blockers using method 
getBlocker(Thread).) The use of these forms rather than the original 
forms without this parameter is strongly encouraged. The normal 
argument to supply as a blocker within a lock implementation is this.

These methods are designed to be used as tools for creating higher-
level synchronization utilities, and are not in themselves useful for 
most concurrency control applications. The park method is designed 
for use only in constructions of the form:

 
 while (!canProceed()) { ... LockSupport.park(this); }

where neither canProceed nor any other actions prior to the call to 
park entail locking or blocking. Because only one permit is associated 
with each thread, any intermediary uses of park could interfere with its 
intended effects.

Sample Usage. Here is a sketch of a first-in-first-out non-reentrant 
lock class:

用於創建鎖和其他同步類的基本線程阻塞原語。

該類關聯一個許可證(參考Semaphore類)。如果許可證可用,park將立即返回,並在此過程中消耗;否則可能會阻塞。如果許可證不可用,則unpark會使許可證可用。 (與信號量不同,許可證不會累積。最多隻有一個。)

方法park和unpark提供了阻塞和解除阻塞線程的有效方法,並且沒有遇到導致方法Thread.suspend和Thread.resume棄用的問題:一個調用park的線程和另一個嘗試unpark的線程之間的競爭由於許可證將保持活動。此外,如果調用者的線程被中斷,並且支持超時版本,則park將返回。 park方法也可以在任何其他時間返回,“無理由”,因此通常必須在循環內重複檢查條件。在這個意義上,park可以作爲“忙碌等待”的優化,不會浪費太多時間自旋,但必須與unpark配對纔能有效。

三種形式的park也支持阻塞對象參數。在線程被阻止時記錄此對象,以允許監視和診斷工具識別線程被阻止的原因。 (這些工具可以使用方法getBlocker(Thread)訪問blockers。)強烈建議使用這些形式而不是沒有此參數的原始形式。在鎖實現中作爲blocker提供的正常參數是this。

這些方法旨在用作創建更高級別同步實用程序的工具,並且對於大多數併發控制應用程序本身並不有用。 park方法僅用於以下形式的結構:

 while (!canProceed()) { ... LockSupport.park(this); }

在調用park之前的canProceed和其他任何行動都不需要鎖定或阻塞。因爲每個線程只有一個許可證,所以任何在此期間使用park都可能會干擾其預期的效果。

示例用法。以下是FIFO非重入鎖的結構草圖:

 class FIFOMutex {
   private final AtomicBoolean locked = new AtomicBoolean(false);
   private final Queue<Thread> waiters
     = new ConcurrentLinkedQueue<Thread>();

   public void lock() {
     boolean wasInterrupted = false;
     Thread current = Thread.currentThread();
     waiters.add(current);

     // Block while not first in queue or cannot acquire lock
     while (waiters.peek() != current ||
            !locked.compareAndSet(false, true)) {
       LockSupport.park(this);
       if (Thread.interrupted()) // ignore interrupts while waiting
         wasInterrupted = true;
     }

     waiters.remove();
     if (wasInterrupted)          // reassert interrupt status on exit
       current.interrupt();
   }

   public void unlock() {
     locked.set(false);
     LockSupport.unpark(waiters.peek());
   }
 }

2.park/unpark

2.1 不帶blocker的原始版本

    public static void park() {
        UNSAFE.park(false, 0L);
    }
Disables the current thread for thread scheduling purposes 
unless the permit is available.

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:
* Some other thread invokes unpark with the current thread as 
  the target; 
* Some other thread interrupts the current thread; 
* The call spuriously (that is, for no reason) returns.

This method does not 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.

除非許可證可用,否則禁用當前線程進行線程調度。

如果許可證可用,那麼它被消耗並且調用立即返回; 否則當前線程禁止進行線程調度,並且在發生以下三種情況之一之前處於休眠狀態:

  • 其他一些線程以當前線程作爲目標調用unpark;
  • 其他一些線程中斷當前線程;
  • 虛假喚醒(即無緣無故)返回。

此方法不會報告這些方法中的哪一個導致返回。調用者應該首先重新檢查導致線程park的條件。 例如,調用者還可以在返回時確定線程的中斷狀態。

    public static void parkNanos(long nanos) {
        if (nanos > 0)
            UNSAFE.park(false, nanos);
    }

最多休眠nanos時間,在park基礎上加一條:

  • 如果定時已到,也會返回
    public static void parkUntil(long deadline) {
        UNSAFE.park(true, deadline);
    }

2.2 帶有blocker的版本(推薦使用)

blocker:the synchronization object responsible for this thread parking

    public static void park(Object blocker) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, 0L);
        setBlocker(t, null);
    }
    public static void parkNanos(Object blocker, long nanos) {
        if (nanos > 0) {
            Thread t = Thread.currentThread();
            setBlocker(t, blocker);
            UNSAFE.park(false, nanos);
            setBlocker(t, null);
        }
    }
    public static void parkUntil(Object blocker, long deadline) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(true, deadline);
        setBlocker(t, null);
    }

2.3 unpark

    public static void unpark(Thread thread) {
        if (thread != null)
            UNSAFE.unpark(thread);
    }
Makes available the permit for the given thread, if it was not 
already available. If the thread was blocked on park then it will 
unblock. Otherwise, its next call to park is guaranteed not to 
block. This operation is not guaranteed to have any effect at all 
if the given thread has not been started.

如果給定線程尚不可用,則爲其提供許可。 如果線程在park時阻塞,則它將解鎖。 否則,它下一次調用park保證不會阻塞。 如果給定的線程尚未啓動,則無法保證此操作有任何效果。

3.Unsafe.park/unpark調用HotSpot層Parker::park/unpark

wz@wz-All-Series:~/openjdk/jdk8u-src$ grep -rn "Unsafe" ./ |grep -v "build" | grep park
./hotspot/src/share/vm/prims/unsafe.cpp:1265:UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
./hotspot/src/share/vm/prims/unsafe.cpp:1266:  UnsafeWrapper("Unsafe_Unpark");

在./hotspot/src/share/vm/prims/unsafe.cpp中有:

UNSAFE_ENTRY(void, Unsafe_Unpark(JNIEnv *env, jobject unsafe, jobject jthread))
  UnsafeWrapper("Unsafe_Unpark");
  Parker* p = NULL;
  if (jthread != NULL) {
    oop java_thread = JNIHandles::resolve_non_null(jthread);
    if (java_thread != NULL) {
      jlong lp = java_lang_Thread::park_event(java_thread);
      if (lp != 0) {
        // This cast is OK even though the jlong might have been read
        // non-atomically on 32bit systems, since there, one word will
        // always be zero anyway and the value set is always the same
        p = (Parker*)addr_from_java(lp);
      } else {
        // Grab lock if apparently null or using older version of library
        MutexLocker mu(Threads_lock);
        java_thread = JNIHandles::resolve_non_null(jthread);
        if (java_thread != NULL) {
          JavaThread* thr = java_lang_Thread::thread(java_thread);
          if (thr != NULL) {
            p = thr->parker();
            if (p != NULL) { // Bind to Java thread for next time.
              java_lang_Thread::set_park_event(java_thread, addr_to_java(p));
            }
          }
        }
      }
    }
  }
  if (p != NULL) {
#ifndef USDT2
    HS_DTRACE_PROBE1(hotspot, thread__unpark, p);
#else /* USDT2 */
    HOTSPOT_THREAD_UNPARK(
                          (uintptr_t) p);
#endif /* USDT2 */
    p->unpark();
  }
UNSAFE_END

最後調用的Parker::unpark方法。

同理對於park也一樣:

UNSAFE_ENTRY(void, Unsafe_Park(JNIEnv *env, jobject unsafe, jboolean isAbsolute, jlong time))
  UnsafeWrapper("Unsafe_Park");
  EventThreadPark event;
#ifndef USDT2
  HS_DTRACE_PROBE3(hotspot, thread__park__begin, thread->parker(), (int) isAbsolute, time);
#else /* USDT2 */
   HOTSPOT_THREAD_PARK_BEGIN(
                             (uintptr_t) thread->parker(), (int) isAbsolute, time);
#endif /* USDT2 */
  JavaThreadParkedState jtps(thread, time != 0);
  thread->parker()->park(isAbsolute != 0, time);
#ifndef USDT2
  HS_DTRACE_PROBE1(hotspot, thread__park__end, thread->parker());
#else /* USDT2 */
  HOTSPOT_THREAD_PARK_END(
                          (uintptr_t) thread->parker());
#endif /* USDT2 */
  if (event.should_commit()) {
    oop obj = thread->current_park_blocker();
    event.set_klass((obj != NULL) ? obj->klass() : NULL);
    event.set_timeout(time);
    event.set_address((obj != NULL) ? (TYPE_ADDRESS) cast_from_oop<uintptr_t>(obj) : 0);
    event.commit();
  }
UNSAFE_END
wz@wz-All-Series:~/openjdk/jdk8u-src$ grep -rn "class Parker" . 
./hotspot/src/share/vm/runtime/park.hpp:48:class Parker : public os::PlatformParker {

class Parker : public os::PlatformParker {
private:
  volatile int _counter ;   //計數
  Parker * FreeNext ;      //指向下一個Parker
  JavaThread * AssociatedWith ; // 指向parker所屬的線程。
 
public:
  Parker() : PlatformParker() {
    _counter       = 0 ;    //初始化爲0
    FreeNext       = NULL ;
    AssociatedWith = NULL ;
  }
protected:
  ~Parker() { ShouldNotReachHere(); }
public:
  // For simplicity of interface with Java, all forms of park (indefinite,
  // relative, and absolute) are multiplexed into one call.
  void park(bool isAbsolute, jlong time);
  void unpark();
 
  // Lifecycle operators
  static Parker * Allocate (JavaThread * t) ;
  static void Release (Parker * e) ;
private:
  static Parker * volatile FreeList ;
  static volatile int ListLock ;
 
};

class PlatformParker : public CHeapObj<mtInternal> {
  protected:
    enum {
        REL_INDEX = 0,
        ABS_INDEX = 1
    };
    int _cur_index;  // 條件變量數組下標,which cond is in use: -1, 0, 1
    pthread_mutex_t _mutex [1] ;  //pthread互斥鎖
    pthread_cond_t  _cond  [2] ; // pthread條件變量數組,一個用於相對時間,一個用於絕對時間。
 
  public:       // TODO-FIXME: make dtor private
    ~PlatformParker() { guarantee (0, "invariant") ; }
 
  public:
    PlatformParker() {
      int status;
      status = pthread_cond_init (&_cond[REL_INDEX], os::Linux::condAttr());
      assert_status(status == 0, status, "cond_init rel");
      status = pthread_cond_init (&_cond[ABS_INDEX], NULL);
      assert_status(status == 0, status, "cond_init abs");
      status = pthread_mutex_init (_mutex, NULL);
      assert_status(status == 0, status, "mutex_init");
      _cur_index = -1; // mark as unused
    }
};

可知park和unpark在linux平臺是藉助於pthread_mutex_t和pthread_cond_t實現的。

3.1 park

public native void park(boolean var1, long var2);

查看底層源碼,位於http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/da3a1f729b2b/src/os/linux/vm/os_linux.cpp


void Parker::park(bool isAbsolute, jlong time) {
  // Ideally we'd do something useful while spinning, such
  // as calling unpackTime().

  // Optional fast-path check:
  // Return immediately if a permit is available.
  // We depend on Atomic::xchg() having full barrier semantics
  // since we are doing a lock-free update to _counter.
  if (Atomic::xchg(0, &_counter) > 0) return;

  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;

  // Optional optimization -- avoid state transitions if there's an interrupt pending.
  // Check interrupt before trying to wait
  if (Thread::is_interrupted(thread, false)) {
    return;
  }

  // Next, demultiplex/decode time arguments
  timespec absTime;
  if (time < 0 || (isAbsolute && time == 0) ) { // don't wait at all
    return;
  }
  if (time > 0) {
    unpackTime(&absTime, isAbsolute, time);
  }


  // Enter safepoint region
  // Beware of deadlocks such as 6317397.
  // The per-thread Parker:: mutex is a classic leaf-lock.
  // In particular a thread must never block on the Threads_lock while
  // holding the Parker:: mutex.  If safepoints are pending both the
  // the ThreadBlockInVM() CTOR and DTOR may grab Threads_lock.
  ThreadBlockInVM tbivm(jt);

  // Don't wait if cannot get lock since interference arises from
  // unblocking.  Also. check interrupt before trying wait
  if (Thread::is_interrupted(thread, false) || pthread_mutex_trylock(_mutex) != 0) {
    return;
  }

  int status ;
  if (_counter > 0)  { // no wait needed
    _counter = 0;
    status = pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
    // Paranoia to ensure our locked and lock-free paths interact
    // correctly with each other and Java-level accesses.
    OrderAccess::fence();
    return;
  }

#ifdef ASSERT
  // Don't catch signals while blocked; let the running threads have the signals.
  // (This allows a debugger to break into the running thread.)
  sigset_t oldsigs;
  sigset_t* allowdebug_blocked = os::Linux::allowdebug_blocked_signals();
  pthread_sigmask(SIG_BLOCK, allowdebug_blocked, &oldsigs);
#endif

  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

  assert(_cur_index == -1, "invariant");
  if (time == 0) {
    _cur_index = REL_INDEX; // arbitrary choice when not timed
    status = pthread_cond_wait (&_cond[_cur_index], _mutex) ;
  } else {
    _cur_index = isAbsolute ? ABS_INDEX : REL_INDEX;
    status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (&_cond[_cur_index]) ;
      pthread_cond_init    (&_cond[_cur_index], isAbsolute ? NULL : os::Linux::condAttr());
    }
  }
  _cur_index = -1;
  assert_status(status == 0 || status == EINTR ||
                status == ETIME || status == ETIMEDOUT,
                status, "cond_timedwait");

#ifdef ASSERT
  pthread_sigmask(SIG_SETMASK, &oldsigs, NULL);
#endif

  _counter = 0 ;
  status = pthread_mutex_unlock(_mutex) ;
  assert_status(status == 0, status, "invariant") ;
  // Paranoia to ensure our locked and lock-free paths interact
  // correctly with each other and Java-level accesses.
  OrderAccess::fence();

  // If externally suspended while waiting, re-suspend
  if (jt->handle_special_suspend_equivalent_condition()) {
    jt->java_suspend_self();
  }
}

park的流程如下:

  • step1.如果有許可可用,則將_counter原子地設置爲0,並直接返回。 xchg返回的是舊的_counter;否則將沒有許可可用。
  • step2.獲取當前線程,如果當前線程設置了中斷標誌,則直接返回,因此如果在park前調用了interrupt就會直接返回。
  • step3.獲取定時時間,安全點;如果中斷或獲取_mutex失敗,則直接返回
  • step4.如果_counter大於0,說明unpark已經調用完成並且將_counter置爲1。所以只需將_counter置爲0,解鎖返回。
  • step5.對於time = 0,pthread_cond_wait (&_cond[_cur_index], _mutex) 直接掛起;
    對於定時的,掛起指定的時間status = os::Linux::safe_cond_timedwait (&_cond[_cur_index], _mutex, &absTime) ;
    ------------------------以下爲線程被喚醒後操作------------------------------
  • step6.將_counter設置爲0,解鎖_mutex

3.2 unpark

public native void unpark(Object var1);

查看底層源碼,位置爲http://hg.openjdk.java.net/jdk8u/jdk8u/hotspot/file/da3a1f729b2b/src/os/linux/vm/os_linux.cpp

void Parker::unpark() {
  int s, status ;
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  _counter = 1;
  if (s < 1) {
    // thread might be parked
    if (_cur_index != -1) {
      // thread is definitely parked
      if (WorkAroundNPTLTimedWaitHang) {
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0, "invariant");
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
      } else {
        // must capture correct index before unlocking
        int index = _cur_index;
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
        status = pthread_cond_signal (&_cond[index]);
        assert (status == 0, "invariant");
      }
    } else {
      pthread_mutex_unlock(_mutex);
      assert (status == 0, "invariant") ;
    }
  } else {
    pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
  }
}
  • step1.對_mutex加鎖,並將_counter置爲1。
  • step2.如果之前的_counter爲0則說明調用了park或者爲初始狀態(此時爲0且沒有調用park)。
  • step2-1.當前parker對應的線程掛起了。因爲_cur_index初始化爲-1,且線程喚醒後也會重置爲-1。
    調用pthread_cond_signal (&_cond[_cur_index])
    調用pthread_mutex_unlock(_mutex)
  • step2-2.沒有線程在等待條件變量,則直接解鎖
    pthread_mutex_unlock(_mutex);
  • step3.如果之前的_counter爲1,則說明線程調用了一次或多次unpark但是沒調用park,則直接解鎖。

3.3 總結

  • park和unpark和核心就是_counter、 _cur_index、 _mutex和_cond。
    通過加鎖_mutex對counter進行操作;
    通過_cond對線程進行掛起和喚醒操作。
  • park和unpark之間的調用先後順序。unpark可在park之前,也可在park之後。
  • 在調用park的時候如果counter是0則會去執行掛起的流程,否則返回,在掛起恢復後再將counter置爲0。
  • 在unpark的時候如果counter是0則會執行喚醒的流程,否則不執行喚醒流程,並且不管什麼情況始終將counter置爲1。
  • 注意在park裏,調用pthread_cond_wait時,並沒有用while來判斷,所以posix condition裏的"Spurious wakeup"一樣會傳遞到上層Java的代碼裏(因爲條件需要Java層才能提供)。這也就是爲什麼Java dos裏提到需要注意虛假喚醒的情況。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章