LockSupport 學習筆記

概念

  • 許可:一種類似信號量((java.util.concurrent.Semaphore)的技術,用來標記當前線程是否允許阻塞。但是不同於信號量可以設置多個信號標識,許可只有一個是否有效的標識。如果許可是有效的,可以理解爲當前線程已經被阻塞;如果許可無效,那麼可以理解線程未被阻塞。

作用

LockSupport 是一個線程同步工具,提供線程的阻塞和取消線程阻塞的功能。LockSupport 的阻塞和取消阻塞的功能是通過類似信號量(java.util.concurrent.Semaphore)技術的許可(permit)來實現的。如果調用阻塞方法 LockSupport.park()時,許可有效,那麼該方法會立刻返回,否則該方法可能會阻塞。
Thread 本身也有類似的方法實現線程阻塞(Thread.suspend)和喚醒(Thread.resume),但是這兩個方法提供的線程阻塞和取消阻塞的方式有死鎖的風險,所以已經被棄用了。LockSupport 提供 park 和 unpark 方法是替換 suspend 和 resume 方法的很好的方式。
除了提供無參數的 park 方法,LockSupport 還提供了帶時間參數的 park 方法,用於設置等待時間。
另外 unpark 可以先於 park 方法調用,增加了使用了靈活性。

Park 方法的實現

public static void park(Object blocker) {
  Thread t = Thread.currentThread();
  setBlocker(t, blocker);
  UNSAFE.park(false, 0L);
  setBlocker(t, null);
}

private static void setBlocker(Thread t, Object arg) {
  // Even though volatile, hotspot doesn't need a write barrier here.
  UNSAFE.putObject(t, parkBlockerOffset, arg);
}

java.lang.Thread 類有一個屬性:volatile Object parkBlocker,當我們調用 park 方法時,會使用 UNSAFE.putObject(t, parkBlockerOffset, arg)對線程的 parkBlocker 屬性設值。
我們可以通過調用 java.util.concurrent.locks.LockSupport.getBlocker 的方式獲取線程阻塞的原因。
LockSupport.park 方法是通過調用底層的 UNSAFE.park(false, 0L)實現線程阻塞的。我們繼續分析 UNSAFE.park 是如何實現的。UNSAFE.park 源碼如下:

  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.
  //Parker 內置了一個_counter 字段,用來記錄許可。如果當_counter>0 時,表示線程可以獲得許可;否則不可以獲得許可。即當調用 park 方法時,如果_counter>0,那麼將_counter 設置爲 0 並返回;
  if (Atomic::xchg(0, &_counter) > 0) return;

  //獲取當前線程,並確認是 Java 線程
  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
  //如果當前線程已經被中斷,那麼 park 方法也直接返回。
  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
    //將 park 內最的_counter 設置爲 0,表示許可已經被使用。
    _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
  //將 java 線程所擁有的操作系統線程設置成 CONDVAR_WAIT 狀態
  OSThreadWaitState osts(thread->osthread(), false /* not Object.wait() */);
  jt->set_suspend_equivalent();
  // cleared by handle_special_suspend_equivalent_condition() or java_suspend_self()

  if (time == 0) {
    //線程設置爲等待狀態。等待的信息是_cond(unpark 方法將會釋放這個信號)
    status = pthread_cond_wait (_cond, _mutex) ;
  } else {
    status = os::Linux::safe_cond_timedwait (_cond, _mutex, &absTime) ;
    if (status != 0 && WorkAroundNPTLTimedWaitHang) {
      pthread_cond_destroy (_cond) ;
      pthread_cond_init    (_cond, NULL);
    }
  }
  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();
  }
}

如代碼中顯示,Parker 內置了一個_counter 字段,用來記錄許可。如果當_counter >0 時,表示線程可以獲得許可;否則不可以獲得許可。即當調用 park 方法時,如果_counter >0,那麼將_counter 設置爲 0 並返回。
park 線程等待和喚醒是通過調用 POSIX 的線程 API,如 pthread_mutex_trylock,pthread_mutex_unlock,pthread_cond_wait,pthread_cond_destroy,pthread_cond_init 等,確保_counter 變量的線程安全和調用操作系統的線程等待和喚醒。unpark 也是一樣。

UnPark 方法的實現

源碼如下:

public static void unpark(Thread thread) {
      if (thread != null)
          UNSAFE.unpark(thread);
  }

hotspot 源碼如下:

void Parker::unpark() {
  int s, status ;
  //添加互斥鎖
  status = pthread_mutex_lock(_mutex);
  assert (status == 0, "invariant") ;
  s = _counter;
  //將_counter 的值設置爲 1。表示有許可可用
  _counter = 1;
  if (s < 1) {
     if (WorkAroundNPTLTimedWaitHang) {
        //發送_cond 信號。park 方法中線程會阻塞等待這個信號纔會喚醒。
        status = pthread_cond_signal (_cond) ;
        assert (status == 0, "invariant") ;
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant") ;
     } else {
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant") ;
        status = pthread_cond_signal (_cond) ;
        assert (status == 0, "invariant") ;
     }
  } else {
    pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
  }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章