park和unpark底層源碼解讀

Unsafe和LockSupport

        Java併發包下的類基本都是基於AQS (AbstractQueuedSynchronizer)框架實現的,關於AQS我在前面講解ReentrantLock源碼的文章中就已經有涉及了。

        -->> 面試難點:深度解析ReentrantLock的實現原理

        而AQS線程安全的實現,又是基於兩個很關鍵的類Unsafe和LockSupport,其中Unsafe主要直接提供CAS操作(關於cas,在文章 讀懂AtomicInteger源碼(多線程專題 )中講解過 ),LockSupport主要提供park/unpark操作,而park/unpark最終調用還是unsafe類,所以unsafe類纔是關鍵。

(如果不會下載JVM源碼可以後臺回覆 “ jdk ”,獲得下載壓縮包)

public static void park() {
    UNSAFE.park(false, 0L);
}
public static void unpark(Thread thread) {
    if (thread != null)
        UNSAFE.unpark(thread);
}
Unsafe類實現:

//park
public native void park(boolean isAbsolute, long time);
 //unpack
public native void unpark(Object var1);
        由代碼可見,Unsafe類的park/unpark是native級別的實現。使用native關鍵字說明這個方法是原生函數,也就是這個方法是用C/C++語言實現的,並且被編譯成了DLL,由java去調用。

        park函數是將當前調用線程阻塞,unpark函數則是將指定線程線程喚醒。

park和unpark作用:

        park是等待一個許可,unpark是爲某線程提供一個許可。如果某線程A調用park,那麼除非另外一個線程調用unpark(A)給A一個許可,否則線程A將阻塞在park操作上。每次調用一次park,需要有一個unpark來解鎖。

        並且unpark可以先於park調用,但是不管unpark先調用幾次,都只提供一個許可,不可疊加,只需要一次park來消費掉unpark帶來的許可,再次調用會阻塞。

Unsafe.park源碼

        在Linux系統下,park和unpark是用的Posix線程庫pthread中的mutex(互斥量),condition(條件變量)來實現的。

        簡單來說,mutex和condition保護了一個叫_counter的信號量。當park時,這個變量被設置爲0,當unpark時,這個變量被設置爲1。當_counter=0 時線程阻塞,當_counter>0直接設爲0並返回。

        每個Java線程都有一個Parker實例,Parker類部分源碼如下:

class Parker : public os::PlatformParker {  
private:  
  volatile int _counter ;  
  ...  
public:  
  void park(bool isAbsolute, jlong time);  
  void unpark();  
  ...  
}  
class PlatformParker : public CHeapObj<mtInternal> {  
  protected:  
    pthread_mutex_t _mutex [1] ;  
    pthread_cond_t  _cond  [1] ;  
    ...  
}
        由源碼可知Parker類繼承於PlatformParker,實際上時用Posix的mutex,condition來實現的。Parker類裏的_counter字段,就是用來記錄park和unpark是否需要阻塞的標識。 

執行過程

        具體的執行邏輯已經用註釋標記在代碼中,簡要來說,就是檢查_counter是不是大於0,如果是,則把_counter設置爲0,返回。如果等於零,繼續執行,阻塞等待。

void Parker::park(bool isAbsolute, jlong time) {
  //判斷信號量counter是否大於0,如果大於設爲0返回
  if (Atomic::xchg(0, &_counter) > 0) return;
 
 
  //獲取當前線程
  Thread* thread = Thread::current();
  assert(thread->is_Java_thread(), "Must be JavaThread");
  JavaThread *jt = (JavaThread *)thread;
 
 
  //如果中途已經是interrupt了,那麼立刻返回,不阻塞
  // Check interrupt before trying to wait
  if (Thread::is_interrupted(thread, false)) {
    return;
  }
 
 
  //記錄當前絕對時間戳
  // Next, demultiplex/decode time arguments
  timespec absTime;
  //如果park的超時時間已到,則返回
  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.
  //進入安全點,利用該thread構造一個ThreadBlockInVM
  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;
  }
 
 
  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");
 
 
  _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();
  }
}


Unsafe.unpark源碼

        unpark直接設置_counter爲1,再unlock mutex返回。如果_counter之前的值是0,則還要調用pthread_cond_signal喚醒在park中等待的線程。

        源碼如下:

void Parker::unpark() {
  //定義兩個變量,staus用於判斷是否獲取鎖
  int s, status ;
  //獲取鎖
  status = pthread_mutex_lock(_mutex);
  //判斷是否成功
  assert (status == 0, "invariant") ;
  //存儲原先變量_counter
  s = _counter;
  //把_counter設爲1
  _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 {
        status = pthread_mutex_unlock(_mutex);
        assert (status == 0, "invariant");
        status = pthread_cond_signal (&_cond[_cur_index]);
        assert (status == 0, "invariant");
      }
    } else {
        //釋放鎖
      pthread_mutex_unlock(_mutex);
      assert (status == 0, "invariant") ;
    }
  } else {
      //釋放鎖
    pthread_mutex_unlock(_mutex);
    assert (status == 0, "invariant") ;
  }
}
 

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