자바 의 synchronized 키워드 (2)

25540 단어
이전 글 은 synchronized 키 워드 를 간략하게 분석 하고 본 고 는 중량급 모니터 의 실현 과 모니터 를 어떻게 얻 는 지 분석 하고 자 한다.
모니터 구현
자바 의 모니터 는 Hotspot 가상 컴퓨터 에서 ObjectMonitor 로 이 루어 집 니 다. ObjectMonitor 류 는 파일 hotspot / src / share / vm / runtime / objectMonitor. hpp 에서 정의 합 니 다. 그 부분 코드 는 다음 과 같 습 니 다.
class ObjectMonitor {
 public:
  enum {
    OM_OK,                    // no error
    OM_SYSTEM_ERROR,          // operating system error
    OM_ILLEGAL_MONITOR_STATE, // IllegalMonitorStateException
    OM_INTERRUPTED,           // Thread.interrupt()
    OM_TIMED_OUT              // Object.wait() timed out
  };
  //      

  // initialize the monitor, exception the semaphore, all other fields
  // are simple integers or pointers
  ObjectMonitor() {
    _header       = NULL;
    _count        = 0;
    _waiters      = 0,
    _recursions   = 0;
    _object       = NULL;
    _owner        = NULL;
    _WaitSet      = NULL;
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;
    FreeNext      = NULL ;
    _EntryList    = NULL ;
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

  bool      try_enter (TRAPS) ;
  void      enter(TRAPS);
  void      exit(bool not_suspended, TRAPS);
  void      wait(jlong millis, bool interruptable, TRAPS);
  void      notify(TRAPS);
  void      notifyAll(TRAPS);
  //      
 private:
  friend class ObjectSynchronizer;
  friend class ObjectWaiter;
  friend class VMStructs;

  // WARNING: this must be the very first word of ObjectMonitor
  // This means this class can't use any virtual member functions.

  volatile markOop   _header;       // displaced object header word - mark
  void*     volatile _object;       // backward object pointer - strong root

  double SharingPad [1] ;           // temp to reduce false sharing

  // All the following fields must be machine word aligned
  // The VM assumes write ordering wrt these fields, which can be
  // read from other threads.

 protected:                         // protected for jvmtiRawMonitor
  void *  volatile _owner;          // pointer to owning thread OR BasicLock
  volatile jlong _previous_owner_tid; // thread id of the previous owner of the monitor
  volatile intptr_t  _recursions;   // recursion count, 0 for first entry
 private:
  int OwnerIsThread ;               // _owner is (Thread *) vs SP/BasicLock
  ObjectWaiter * volatile _cxq ;    // LL of recently-arrived threads blocked on entry.
                                    // The list is actually composed of WaitNodes, acting
                                    // as proxies for Threads.
 protected:
  ObjectWaiter * volatile _EntryList ;     // Threads blocked on entry or reentry.
 private:
  Thread * volatile _succ ;          // Heir presumptive thread - used for futile wakeup throttling
  Thread * volatile _Responsible ;
  int _PromptDrain ;                // rqst to drain cxq into EntryList ASAP

  volatile int _Spinner ;           // for exit->spinner handoff optimization
  volatile int _SpinFreq ;          // Spin 1-out-of-N attempts: success rate
  volatile int _SpinClock ;
  volatile int _SpinDuration ;
  volatile intptr_t _SpinState ;    // MCS/CLH list of spinners

  // TODO-FIXME: _count, _waiters and _recursions should be of
  // type int, or int32_t but not intptr_t.  There's no reason
  // to use 64-bit fields for these variables on a 64-bit JVM.

  volatile intptr_t  _count;        // reference count to prevent reclaimation/deflation
                                    // at stop-the-world time.  See deflate_idle_monitors().
                                    // _count is approximately |_WaitSet| + |_EntryList|
 protected:
  volatile intptr_t  _waiters;      // number of waiting threads
 private:
 protected:
  ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor
 private:
  volatile int _WaitSetLock;        // protects Wait Queue - simple spinlock
  //      
}

Object Monitor 클래스 의 중요 한 구성원 변수 와 함 수 는 다음 과 같 습 니 다.
  • _header: 모니터 가 속 한 대상 의 mark word;
  • _object: 모니터 가 속 한 대상;
  • _owner: 잠 금 기록 이나 모니터 를 가 진 스 레 드 를 가리 키 는 경우;
  • OwnerIsThread:_owner 변 수 는 스 레 드 포인터 일 때 1 이 고 잠 금 기록 포인터 일 때 0 입 니 다.
  • _Entry List: 양 방향 링크 는 입구 에 저장 하거나 막 힌 스 레 드 에 다시 저장 합 니 다.
  • _WaitSet: 양 방향 링크, 기다 리 는 스 레 드 저장;
  • _cxq: 단 방향 링크, 입구 가 막 힌 최근 도착 한 스 레 드 (Recently Arrived Threads, RATs) 에 저장 합 니 다.
  • 구조 함 수 는 구성원 변수 에 초기 값 을 부여 할 뿐 포인터 변 수 는 모두 NULL 로 할당 되 고 정형 변 수 는 모두 0 으로 할당 된다.
  • try_enter, enter, exit, wait, notify, notify All 등 함 수 는 모두 모니터 를 가 져 오고 방출 하 는 것 과 관련 이 있 습 니 다.

  • 요점 설명
    파일 object Monitor. cpp 의 설명 이 유용 하여 모니터 를 이해 하 는 데 도움 이 됩 니 다.
  • 스 레 드 성공 실행 을 통 해owner 가 NULL 에서 비 NULL 의 CAS 작업 으로 변경 하여 모니터 를 가 져 옵 니 다.
  • 스 레 드 는 특정한 시간 에 한 링크 에 만 나타 나 거나 cxq 또는 Entry List 또는 Wait Set;
  • 경쟁 스 레 드 는 CAS 를 사용 하여 스스로 cxq 를 추진 한 다음 에 자전 또는 Park;
  • 경쟁 스 레 드 가 마지막 으로 자 물 쇠 를 얻 은 후에 반드시 자신 을 Entry List 또는 cxq 에서 팀 에서 나 가 야 합 니 다.
  • 탈퇴 스 레 드 는 Entry List 에서 '법정 상속인' 을 식별 하고 unpark 를 식별 합 니 다. 탈퇴 스 레 드 는 이 후계 스 레 드 를 Entry List 에서 링크 를 해제 하지 않 았 습 니 다. unpark 이후 에 깨 어 난 스 레 드 는 모니터 의 소유권 을 다시 경쟁 합 니 다. 잠 금 을 얻 거나 다시 Park 자신 을 얻 을 수 있 습 니까?종료 스 레 드 는 후계 스 레 드 에 소유권 을 전달 하 는 것 을 보장 하지 않 습 니 다. (즉, 이것 은 손 으로 전달 하 는 것 이 아 닙 니 다) EntryList 가 비어 있 지만 cxq 가 비어 있 지 않 으 면 종료 스 레 드 는 cxq (CAS 를 이용 하여 cxq 링크 를 NULL 로 설정) 를 분리 하고 cxq 의 요 소 를 EntryList 로 이동 합 니 다.
  • 모니터 소유자 만 EntryList 에 접근 하거나 변경 할 수 있 습 니 다.
  • 모니터 소유자 만 cxq 를 분리 할 수 있 습 니 다.
  • notify () 또는 notifyAll () 은 WaitSet 의 스 레 드 를 Entry List 나 cxq 로 옮 길 뿐 입 니 다.

  • CAS 조작
    모니터 와 관련 된 함 수 를 분석 할 때 CAS 조작 을 이해 하 는 것 이 중요 하 다.Hotspot 가상 컴퓨터 의 CAS 작업 은 Atomic 류 의 cmpxchg 와 cmpxchgptr 정적 함수 구현, 일반적으로 CAS 작업 은 프로세서 의 특정 명령 에 의 해 지 원 됩 니 다.x86 의 Linux 시스템 을 예 로 들 면 cmpxchg 와 cmpxchgptr 함수 정의 파일 hotspot / src / oscpu/linux_x86/vm/atomic_linux_x86. inline. hpp 에서 코드 는 다음 과 같 습 니 다.
    inline jlong    Atomic::cmpxchg    (jlong    exchange_value, volatile jlong*    dest, jlong    compare_value) {
      bool mp = os::is_MP();
      __asm__ __volatile__ (LOCK_IF_MP(%4) "cmpxchgq %1,(%3)"
                            : "=a" (exchange_value)
                            : "r" (exchange_value), "a" (compare_value), "r" (dest), "r" (mp)
                            : "cc", "memory");
      return exchange_value;
    }
    
    inline void*    Atomic::cmpxchg_ptr(void*    exchange_value, volatile void*     dest, void*    compare_value) {
      return (void*)cmpxchg((jlong)exchange_value, (volatile jlong*)dest, (jlong)compare_value);
    }
    

    GCC 내 연 어 셈 블 리 문법 에 따 르 면 cmpxchg 함수 의 의 미 는 comparevalue 와 dest 주소 가 인용 한 내용 이 같 으 면 compare 로 돌아 갑 니 다.value 동시 exchangevalue 가 dest 주소 에 기록 되 었 습 니 다.그렇지 않 으 면 dest 주소 에서 인용 한 내용 을 되 돌려 줍 니 다.
    모니터 가 져 오기
    enter 함수
    Object Monitor 류 의 enter 함 수 는 모니터 를 얻 는 데 사 용 됩 니 다. 코드 는 다음 과 같 습 니 다.
    void ATTR ObjectMonitor::enter(TRAPS) { // TRAPS      Thread* __the_thread__
      // The following code is ordered to check the most common cases first
      // and to reduce RTS->RTO cache line upgrades on SPARC and IA32 processors.
      Thread * const Self = THREAD ; // THREAD      __the_thread__
      void * cur ;
    
      cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
      if (cur == NULL) { // CAS    ,  CAS   _owner NULL,           ,CAS     _owner       
         // Either ASSERT _recursions == 0 or explicitly set _recursions = 0.
         assert (_recursions == 0   , "invariant") ;
         assert (_owner      == Self, "invariant") ;
         // CONSIDER: set or assert OwnerIsThread == 1
         return ;
      }
    
      if (cur == Self) { // CAS         ,CAS   _owner        ,     
         // TODO-FIXME: check for integer overflow!  BUGID 6557169.
         _recursions ++ ;
         return ;
      }
      // CAS          ,CAS   _owner           ,                        ,                 
      if (Self->is_lock_owned ((address)cur)) {
        assert (_recursions == 0, "internal state error");
        _recursions = 1 ;
        // Commute owner from a thread-specific on-stack BasicLockObject address to
        // a full-fledged "Thread *".
        _owner = Self ;
        OwnerIsThread = 1 ; //   _owner     
        return ;
      }
      // CAS         ,      
      // We've encountered genuine contention.
      assert (Self->_Stalled == 0, "invariant") ;
      Self->_Stalled = intptr_t(this) ;
    
      // Try one round of spinning *before* enqueueing Self
      // and before going through the awkward and expensive state
      // transitions.  The following spin is strictly optional ...
      // Note that if we acquire the monitor from an initial spin
      // we forgo posting JVMTI events and firing DTRACE probes.
      if (Knob_SpinEarly && TrySpin (Self) > 0) { //        ,            , TrySpin     0                
         assert (_owner == Self      , "invariant") ;
         assert (_recursions == 0    , "invariant") ;
         assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
         Self->_Stalled = 0 ;
         return ;
      }
    
      assert (_owner != Self          , "invariant") ;
      assert (_succ  != Self          , "invariant") ;
      assert (Self->is_Java_thread()  , "invariant") ;
      JavaThread * jt = (JavaThread *) Self ;
      assert (!SafepointSynchronize::is_at_safepoint(), "invariant") ;
      assert (jt->thread_state() != _thread_blocked   , "invariant") ;
      assert (this->object() != NULL  , "invariant") ;
      assert (_count >= 0, "invariant") ;
    
      // Prevent deflation at STW-time.  See deflate_idle_monitors() and is_busy().
      // Ensure the object-monitor relationship remains stable while there's contention.
      Atomic::inc_ptr(&_count);
    
      EventJavaMonitorEnter event;
    
      { // Change java thread status to indicate blocked on monitor enter.
        JavaThreadBlockedOnMonitorEnterState jtbmes(jt, this);
    
        Self->set_current_pending_monitor(this);
    
        DTRACE_MONITOR_PROBE(contended__enter, this, object(), jt);
        if (JvmtiExport::should_post_monitor_contended_enter()) {
          JvmtiExport::post_monitor_contended_enter(jt, this);
    
          // The current thread does not yet own the monitor and does not
          // yet appear on any queues that would get it made the successor.
          // This means that the JVMTI_EVENT_MONITOR_CONTENDED_ENTER event
          // handler cannot accidentally consume an unpark() meant for the
          // ParkEvent associated with this ObjectMonitor.
        }
    
        OSThreadContendState osts(Self->osthread());
        ThreadBlockInVM tbivm(jt);
    
        // TODO-FIXME: change the following for(;;) loop to straight-line code.
        for (;;) {
          jt->set_suspend_equivalent();
          // cleared by handle_special_suspend_equivalent_condition()
          // or java_suspend_self()
    
          EnterI (THREAD) ;
    
          if (!ExitSuspendEquivalent(jt)) break ;
    
          //
          // We have acquired the contended monitor, but while we were
          // waiting another thread suspended us. We don't want to enter
          // the monitor while suspended because that would surprise the
          // thread that suspended us.
          //
              _recursions = 0 ;
          _succ = NULL ;
          exit (false, Self) ;
    
          jt->java_suspend_self();
        }
        Self->set_current_pending_monitor(NULL);
    
        // We cleared the pending monitor info since we've just gotten past
        // the enter-check-for-suspend dance and we now own the monitor free
        // and clear, i.e., it is no longer pending. The ThreadBlockInVM
        // destructor can go to a safepoint at the end of this block. If we
        // do a thread dump during that safepoint, then this thread will show
        // as having "-locked" the monitor, but the OS and java.lang.Thread
        // states will still report that the thread is blocked trying to
        // acquire it.
      }
    
      Atomic::dec_ptr(&_count);
      assert (_count >= 0, "invariant") ;
      Self->_Stalled = 0 ;
    
      // Must either set _recursions = 0 or ASSERT _recursions == 0.
      assert (_recursions == 0     , "invariant") ;
      assert (_owner == Self       , "invariant") ;
      assert (_succ  != Self       , "invariant") ;
      assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
    
      //       
    }
    
  • 먼저 CAS 작업 을 한 번 진행 합 니 다. 교환 값 은 enter 함수 매개 변수 스 레 드 포인터 이 고 목적 주 소 는 모니터 의 입 니 다.owner 필드 주소, 비교 값 은 NULL 입 니 다.CAS 조작 에 성공 하면 CAS 조작 전owner 는 NULL 입 니 다. 모니터 는 스 레 드 에 보유 되 지 않 았 습 니 다. CAS 작업 성공 후owner 의 값 은 매개 변수 스 레 드 (즉, 위의 요점 설명 의 첫 번 째 점) 입 니 다.
  • 이어서 CAS 작업 이 실패 한 상황 을 처리 합 니 다. CAS 작업 전owner 는 이미 매개 변수 라인 입 니 다. 이것 은 다시 들 어 갑 니 다.
  • 그리고 CAS 작업 에 실패 한 또 다른 상황 을 처리 합 니 다. CAS 작업 전owner 는 특정한 잠 금 기록 을 가리 키 는 지침 입 니 다. 이 잠 금 기록 이 매개 변수 스 레 드 에 의 해 스 택 정 에 분배 되 는 지 확인 하 십시오. 만약 에 이 스 레 드 가 이 모니터 를 가지 고 있다 는 것 을 설명 합 니 다.
  • 마지막 으로 CAS 조작 이 실패 한 다른 상황, 진정한 잠 금 경쟁 이다.먼저 비 싼 상태 로 이동 하 는 작업 을 피하 기 위해 자전 을 시도 합 니 다. 만약 에 TrySpin 의 반환 값 이 0 보다 크 면 자전 할 때 모니터 를 얻 었 다 는 것 을 의미 합 니 다. 그렇지 않 으 면 EnterI 함수 경쟁 모니터 를 실행 합 니 다.

  • EnterI 함수
    EnterI 함수 의 코드 는 다음 과 같 습 니 다. 두 번 째 for 순환 은 상기 요점 이 설명 한 세 번 째 점 을 증명 합 니 다.
    void ATTR ObjectMonitor::EnterI (TRAPS) {
        Thread * Self = THREAD ;
        assert (Self->is_Java_thread(), "invariant") ;
        assert (((JavaThread *) Self)->thread_state() == _thread_blocked   , "invariant") ;
    
        // Try the lock - TATAS
        if (TryLock (Self) > 0) { //      
            assert (_succ != Self              , "invariant") ;
            assert (_owner == Self             , "invariant") ;
            assert (_Responsible != Self       , "invariant") ;
            return ;
        }
    
        DeferredInitialize () ;
    
        // We try one round of spinning *before* enqueueing Self.
        //
        // If the _owner is ready but OFFPROC we could use a YieldTo()
        // operation to donate the remainder of this thread's quantum
        // to the owner.  This has subtle but beneficial affinity
        // effects.
    
        if (TrySpin (Self) > 0) { //           
            assert (_owner == Self        , "invariant") ;
            assert (_succ != Self         , "invariant") ;
            assert (_Responsible != Self  , "invariant") ;
            return ;
        }
    
        // The Spin failed -- Enqueue and park the thread ...
        assert (_succ  != Self            , "invariant") ;
        assert (_owner != Self            , "invariant") ;
        assert (_Responsible != Self      , "invariant") ;
    
        // Enqueue "Self" on ObjectMonitor's _cxq.
        //
        // Node acts as a proxy for Self.
        // As an aside, if were to ever rewrite the synchronization code mostly
        // in Java, WaitNodes, ObjectMonitors, and Events would become 1st-class
        // Java objects.  This would avoid awkward lifecycle and liveness issues,
        // as well as eliminate a subset of ABA issues.
        // TODO: eliminate ObjectWaiter and enqueue either Threads or Events.
        //
        //     ,        ObjectWaiter   cxq  
        ObjectWaiter node(Self) ;
        Self->_ParkEvent->reset() ;
        node._prev   = (ObjectWaiter *) 0xBAD ; //        ,BAD :)
        node.TState  = ObjectWaiter::TS_CXQ ;
    
        // Push "Self" onto the front of the _cxq.
        // Once on cxq/EntryList, Self stays on-queue until it acquires the lock.
        // Note that spinning tends to reduce the rate at which threads
        // enqueue and dequeue on EntryList|cxq.
        ObjectWaiter * nxt ;
        for (;;) { //      _cxq node   , CAS                 ,        ,  CAS     
            node._next = nxt = _cxq ;
            if (Atomic::cmpxchg_ptr (&node, &_cxq, nxt) == nxt) break ;
    
            // Interference - the CAS failed because _cxq changed.  Just retry.
            // As an optional optimization we retry the lock.
            if (TryLock (Self) > 0) {
                assert (_succ != Self         , "invariant") ;
                assert (_owner == Self        , "invariant") ;
                assert (_Responsible != Self  , "invariant") ;
                return ;
            }
        }
    
        //       
    
        for (;;) {
    
            if (TryLock (Self) > 0) break ;
            assert (_owner != Self, "invariant") ;
    
            if ((SyncFlags & 2) && _Responsible == NULL) {
               Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
            }
    
            // park self
            if (_Responsible == Self || (SyncFlags & 1)) {
                TEVENT (Inflated enter - park TIMED) ;
                Self->_ParkEvent->park ((jlong) RecheckInterval) ;
                // Increase the RecheckInterval, but clamp the value.
                RecheckInterval *= 8 ;
                if (RecheckInterval > 1000) RecheckInterval = 1000 ;
            } else {
                TEVENT (Inflated enter - park UNTIMED) ;
                Self->_ParkEvent->park() ;
            }
    
            if (TryLock(Self) > 0) break ;
    
            // The lock is still contested.
            // Keep a tally of the # of futile wakeups.
            // Note that the counter is not protected by a lock or updated by atomics.
            // That is by design - we trade "lossy" counters which are exposed to
            // races during updates for a lower probe effect.
            TEVENT (Inflated enter - Futile wakeup) ;
            if (ObjectMonitor::_sync_FutileWakeups != NULL) {
               ObjectMonitor::_sync_FutileWakeups->inc() ;
            }
            ++ nWakeups ;
    
            // Assuming this is not a spurious wakeup we'll normally find _succ == Self.
            // We can defer clearing _succ until after the spin completes
            // TrySpin() must tolerate being called with _succ == Self.
            // Try yet another round of adaptive spinning.
            if ((Knob_SpinAfterFutile & 1) && TrySpin (Self) > 0) break ;
    
            // We can find that we were unpark()ed and redesignated _succ while
            // we were spinning.  That's harmless.  If we iterate and call park(),
            // park() will consume the event and return immediately and we'll
            // just spin again.  This pattern can repeat, leaving _succ to simply
            // spin on a CPU.  Enable Knob_ResetEvent to clear pending unparks().
            // Alternately, we can sample fired() here, and if set, forgo spinning
            // in the next iteration.
    
            if ((Knob_ResetEvent & 1) && Self->_ParkEvent->fired()) {
               Self->_ParkEvent->reset() ;
               OrderAccess::fence() ;
            }
            if (_succ == Self) _succ = NULL ;
    
            // Invariant: after clearing _succ a thread *must* retry _owner before parking.
            OrderAccess::fence() ;
        }
        //           park     ,               
        // Egress :
        // Self has acquired the lock -- Unlink Self from the cxq or EntryList.
        // Normally we'll find Self on the EntryList .
        // From the perspective of the lock owner (this thread), the
        // EntryList is stable and cxq is prepend-only.
        // The head of cxq is volatile but the interior is stable.
        // In addition, Self.TState is stable.
    
        assert (_owner == Self      , "invariant") ;
        assert (object() != NULL    , "invariant") ;
        // I'd like to write:
        //   guarantee (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ;
        // but as we're at a safepoint that's not safe.
    
        UnlinkAfterAcquire (Self, &node) ; // Self        ,    EntryList    cxq    
        if (_succ == Self) _succ = NULL ;
    
        //      
        return ;
    }
    
  • 먼저 TryLock 함 수 를 호출 하여 자 물 쇠 를 가 져 오 려 고 시도 하고 실패 하면 다시 자전 해 보십시오.
  • 자전 에 실패 하면 매개 변수 스 레 드 를 ObjectWaiter 로 포장 하고 cxq 팀 의 첫 번 째 에 가입 합 니 다.
  • 체인 헤더 업데이트cxq 는 이전 ObjectWaiter 의 주소 입 니 다. CAS 를 사용 하 는 것 은 여러 개의 스 레 드 가 동시에 모니터 를 경쟁 할 수 있 기 때문에 이 코드 를 실행 하고 있 습 니 다. CAS 가 실패 하면 다시 시도 합 니 다.
  • 두 번 째 for 순환 에서 TryLock 함 수 를 이용 하여 모니터 를 계속 얻 으 려 고 시도 합 니 다. 실패 하면 ParkEvent 류 의 Park 함 수 를 호출 하여 자신 을 차단 합 니 다 (글 자바 스 레 드 의 중단 과 휴면 에서 Park 함 수 를 분석 한 적 이 있 습 니 다).성공 하면 순환 을 뛰 어 넘 습 니 다. 이 때 는 모니터 를 가지 고 있 을 것 입 니 다.
  • 매개 변수 스 레 드 가 모니터 를 얻 은 후 Unlink AfterAcquire 함 수 를 호출 하여 자신 을 Entry List 링크 나 cxq 링크 에서 삭제 합 니 다 (즉, 상기 요점 설명 의 네 번 째 점).

  • TryLock 함수
    TryLock 함수 기능 은 비교적 간단 합 니 다. 그 코드 는 다음 과 같 습 니 다.
    // Caveat: TryLock() is not necessarily serializing if it returns failure.
    // Callers must compensate as needed.
    
    int ObjectMonitor::TryLock (Thread * Self) {
       for (;;) {
          void * own = _owner ;
          if (own != NULL) return 0 ; // _owner  NULL,                
          if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) { // CAS    ,  CAS   _owner NULL,           ,CAS   _owner       
             // Either guarantee _recursions == 0 or set _recursions = 0.
             assert (_recursions == 0, "invariant") ;
             assert (_owner == Self, "invariant") ;
             // CONSIDER: set or assert that OwnerIsThread == 1
             return 1 ;
          }
          // The lock had been free momentarily, but we lost the race to the lock.
          // Interference -- the CAS failed.
          // We can either return -1 or retry.
          // Retry doesn't make as much sense because the lock was just acquired.
          if (true) return -1 ;
       }
    }
    
  • 모니터 가 다른 스 레 드 에 점유 되 어 있 으 면 0 으로 돌아 갑 니 다.
  • CAS 작업 이 성공 적 으로 매개 변수 스 레 드 가 이 모니터 를 가지 고 있 으 면 1 을 되 돌려 줍 니 다.
  • CAS 작업 이 실패 하면 다른 스 레 드 는 이 모니터 를 가지 고 있 습 니 다. - 1 로 돌아 갑 니 다.

  • UninkAfterAcquire 함수
    Unlink AfterAcquire 함수 코드 는 다음 과 같 습 니 다. 매개 변수 스 레 드 가 모니터 를 얻 은 후에 자신 을 Entry List 양 방향 링크 나 cxq 단 방향 링크 에서 삭제 하 는 역할 을 합 니 다.
    // after the thread acquires the lock in ::enter().  Equally, we could defer
    // unlinking the thread until ::exit()-time.
    
    void ObjectMonitor::UnlinkAfterAcquire (Thread * Self, ObjectWaiter * SelfNode)
    {
        assert (_owner == Self, "invariant") ;
        assert (SelfNode->_thread == Self, "invariant") ;
    
        if (SelfNode->TState == ObjectWaiter::TS_ENTER) { //  EntryList  
            // Normal case: remove Self from the DLL EntryList .
            // This is a constant-time operation.
            ObjectWaiter * nxt = SelfNode->_next ;
            ObjectWaiter * prv = SelfNode->_prev ;
            if (nxt != NULL) nxt->_prev = prv ;
            if (prv != NULL) prv->_next = nxt ;
            if (SelfNode == _EntryList ) _EntryList = nxt ;
            assert (nxt == NULL || nxt->TState == ObjectWaiter::TS_ENTER, "invariant") ;
            assert (prv == NULL || prv->TState == ObjectWaiter::TS_ENTER, "invariant") ;
            TEVENT (Unlink from EntryList) ;
        } else { //  cxq  
            guarantee (SelfNode->TState == ObjectWaiter::TS_CXQ, "invariant") ;
            // Inopportune interleaving -- Self is still on the cxq.
            // This usually means the enqueue of self raced an exiting thread.
            // Normally we'll find Self near the front of the cxq, so
            // dequeueing is typically fast.  If needbe we can accelerate
            // this with some MCS/CHL-like bidirectional list hints and advisory
            // back-links so dequeueing from the interior will normally operate
            // in constant-time.
            // Dequeue Self from either the head (with CAS) or from the interior
            // with a linear-time scan and normal non-atomic memory operations.
            // CONSIDER: if Self is on the cxq then simply drain cxq into EntryList
            // and then unlink Self from EntryList.  We have to drain eventually,
            // so it might as well be now.
    
            ObjectWaiter * v = _cxq ; //    EnterI    Self  cxq         ,      cxq      
            assert (v != NULL, "invariant") ;
            if (v != SelfNode || Atomic::cmpxchg_ptr (SelfNode->_next, &_cxq, v) != v) {
                // The CAS above can fail from interference IFF a "RAT" arrived.
                // In that case Self must be in the interior and can no longer be
                // at the head of cxq.
                if (v == SelfNode) { //   if         :  if  v == SelfNode,     if   v != SelfNode  ,  CAS    ,  cxq      ,   v     
                    assert (_cxq != v, "invariant") ;
                    v = _cxq ;          // CAS above failed - start scan at head of list
                }
                ObjectWaiter * p ;
                ObjectWaiter * q = NULL ;
                for (p = v ; p != NULL && p != SelfNode; p = p->_next) { // v  cxq  
                    q = p ;
                    assert (p->TState == ObjectWaiter::TS_CXQ, "invariant") ;
                }
                assert (v != SelfNode,  "invariant") ;
                assert (p == SelfNode,  "Node not found on cxq") ;
                assert (p != _cxq,      "invariant") ;
                assert (q != NULL,      "invariant") ;
                assert (q->_next == p,  "invariant") ;
                q->_next = p->_next ;
            }
            TEVENT (Unlink from cxq) ;
        }
    
        // Diagnostic hygiene ...
        SelfNode->_prev  = (ObjectWaiter *) 0xBAD ;
        SelfNode->_next  = (ObjectWaiter *) 0xBAD ;
        SelfNode->TState = ObjectWaiter::TS_RUN ;
    }
    

    참고 문헌
    http://www.diva-portal.org/smash/get/diva2:754541/FULLTEXT01.pdf https://blog.csdn.net/penngrove/article/details/44175387 https://www.jianshu.com/p/1782e14a0766 https://pdfs.semanticscholar.org/edf9/54412a9b1ce955bea148199f325759779540.pdf

    좋은 웹페이지 즐겨찾기