Java锁总结,synchronized&ReentrantLock深入分析

一、锁的分类

1.1 可重入锁、不可重入锁

重入:当前线程获取到A锁,在获取之后尝试再次获取A锁是可以直接拿到的

不可重入:当前线程获取到A锁,在获取之后尝试获取A锁,无法获取到的,因为A锁被当前线程占用着,需要等待自己释放锁在获取锁。

1.2 乐观锁、悲观锁

Java中提供的synchronized、ReentrantLock、ReentrantReadWriteLock都是悲观锁

Java中提供的CAS操作,就是乐观锁的一种实现

悲观锁:获取不到锁资源时,会将当前线程挂起(进入BLOCKED、WAITING),线程挂起会涉及到用户态和内核态的切换。而这种切换是比较消耗资源的

  • 用户态:JVM可以自行执行的指令,不需要借助操作系统执行
  • 内核态:JVM不可以自行执行,需要操作系统才可以执行

乐观锁:获取不到锁资源、可以再次让CPU调度,重新尝试获取锁资源

Atomic原子性类中,就是基于CAS乐观锁实现的

1.3 公平锁、非公平锁

Java提供的synchronized只能是非公平锁

Java中提供的ReentrantLock、ReentrantReadWriteLock可以实现公平锁和非公平锁

公平锁:线程A获取到了锁资源。线程B没有拿到,线程B去排队,线程C来了,锁被A持有,同时线程B在排队。直接排队B的后面,等待B拿到锁资源或者是B取消后,才可以尝试去竞争锁资源

非公平锁:线程A拿到了锁资源,线程B没有拿到,线程B去排队,线程C来了,先尝试竞争一波

  • 拿到锁资源:插队成功
  • 没有拿到锁资源:依然排到B的后面,等待B拿到锁资源或者是B取消后,才可以尝试去竞争锁资源

1.4 互斥锁、共享锁

Java提供了synchronized、ReentrantLock是互斥锁

Java提供的ReentrantReadWriteLock,有互斥锁也有共享锁

互斥锁:同一时间点、只会有个线程持有者当前互斥锁。

共享锁:同一个时间点,当前共享锁可以被多个线程同时持有

二、深入synchronized

2.1 类锁、对象锁

synchronized的使用一般就是同步方法和同步代码块

synchronized的锁是基于对象实现的

如果使用同步方法

  • static:此时使用的当前类.class作为锁(类锁)
  • 非static:此时使用的是当前对象作为锁(对象锁)
public class SyncITest {
    public static void main (String[] args) {
        // 锁的是TSync.class 类锁
        TSync.a();

        TSync tSync = new TSync();
        // 锁的是tSync对象,对象锁
        tSync.b();
    }
   
}
class TSync {
    public static synchronized void a(){
        System.out.println("hello word");
    }
    public  synchronized void b(){
        System.out.println("hello word");
    }
}

2.2 synchronized的优化

在JDK1.5的时候,Doug Lee 推出了ReentratLock,lock的性能远高于synchronized,所以JDK团队在JDK1.6中,对synchronized做了大量的优化

锁消除: 在synchronized修饰的代码中,如果不存咋操作临界资源的情况,会触发锁消除,即便写了synchronized,也不会触发

public synchronized void method(){
    // 没有操作临界资源
    // 此时这个方法的synchronized你可以认为木有~~
}

锁膨胀: 如果在一个循环中,频繁的获取和释放锁资源,这样带来的消耗很大,锁膨胀就是锁的范围扩大,避免频繁的竞争和获取锁资源带来不必要的消耗

public void method(){
    for(int i = 0;i < 999999;i++){
        synchronized(对象){

        }
    }
    // 这是上面的代码会触发锁膨胀
    synchronized(对象){
        for(int i = 0;i < 999999;i++){

        }
    }
}

**锁升级:**ReentrantLock的实现,是先基于乐观锁的CAS尝试获取锁资源,如果拿不到锁资源,才会挂起线程,synchronized在JDK1.6之前,完全就是获取不到锁,立即挂起当前线程,所以synchronized性能比较差

synchronized就在JDK1.6做了锁升价的优化

  • 无锁、匿名偏向:当前对象没有作为锁存在。
  • 偏向锁:如果当前锁资源,只有一个线程在频繁的获取和释放,那么这个线程过来,只需要判断,当前指向的线程是否是当前线程。
    • 如果是,直接拿锁资源走
    • 如果当前线程不是我,基于CAS的方式,尝试将偏向锁指向当前线程,如果获取不到,触发锁升级,升级为轻量级锁,(偏向锁状态出现了锁竞争的情况)
  • 轻量级锁:会采用自旋锁的方式频繁的以CAS的方式获取锁资源(采用的是自适应自旋锁
    • 如果成功获取到,拿着锁资源走
    • 如果自旋了一定次数,没拿到锁资源,锁升级
  • 重量级锁:就是最传统的synchronized方式,拿不到锁资源,就挂起当前线程。(用户态&内核态)

2.3 synchronized实现原理

synchronized是基于对象实现的

先要对Java中的对象在堆内存的存储有一个了解

image.png

MarkWord

image.png

MarkWord中标记着四种锁的信息

  • 无锁 : 偏向锁一位 0,锁标记两位 0 1,0 0 1
  • 偏向锁:偏向锁一位 1,锁标记两位 0 1,1 0 1
  • 轻量级锁:锁标记两位 0 0
  • 重量级锁:锁标记两位 1 0

2.4 synchronized的锁升级

为了可以在Java中看到对象头的MarkWord信息,需要导入依赖

<dependency>
    <groupId>org.openjdk.jol</groupId>
    <artifactId>jol-core</artifactId>
    <version>0.9</version>
</dependency>

锁默认情况下,开启了偏向锁延迟

偏向锁在升级位轻量级锁时,会涉及到偏向锁撤销,需要等到一个安全的(STW),才可以做偏向锁撤销,在明知道有并发情况,就可以选择不开启偏向锁,或者是设置偏向锁延迟开启

因为JVM启动时,需要加载大量的.class文件到内存中,这个操作会涉及到synchronized的使用,为了避免出现偏向锁撤销操作,JVM启动初期,有一个延迟4s开启偏向锁的操作

如果正常开启偏向锁了,那么会出现无锁状态,对象会直接变为匿名偏向

public class MarkWord {
    public static void main (String[] args) throws Exception {
        Object o = new Object();
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        Thread.sleep(5000);
        System.out.println(ClassLayout.parseInstance(o).toPrintable());
        new Thread(() -> {
            synchronized (o) {
                // 偏向锁
                System.out.println(Thread.currentThread().getName() + ": ");
                System.out.println(ClassLayout.parseInstance(o).toPrintable());
            }
        }).start();

        // mian - 偏向锁 - 轻量级锁CAS - 重量级锁
        synchronized (o) {
            // 偏向锁
            System.out.println(Thread.currentThread().getName() + ": ");
            System.out.println(ClassLayout.parseInstance(o).toPrintable());
        }
    }
//    java.lang.Object object internals:
//    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
//      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
//            4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//            8     4        (object header)                           00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
//            12     4        (loss due to the next object alignment)
//    Instance size: 16 bytes
//    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
//
//    java.lang.Object object internals:
//    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
//      0     4        (object header)                           05 00 00 00 (00000101 00000000 00000000 00000000) (5)
//            4     4        (object header)                           00 00 00 00 (00000000 00000000 00000000 00000000) (0)
//            8     4        (object header)                           00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
//            12     4        (loss due to the next object alignment)
//    Instance size: 16 bytes
//    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
//
//    main:
//    java.lang.Object object internals:
//    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
//      0     4        (object header)                           05 08 15 dd (00000101 00001000 00010101 11011101) (-585824251)
//            4     4        (object header)                           54 02 00 00 (01010100 00000010 00000000 00000000) (596)
//            8     4        (object header)                           00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
//            12     4        (loss due to the next object alignment)
//    Instance size: 16 bytes
//    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
//
//    Thread-0:
//    java.lang.Object object internals:
//    OFFSET  SIZE   TYPE DESCRIPTION                               VALUE
//      0     4        (object header)                           02 79 e2 8a (00000010 01111001 11100010 10001010) (-1964869374)
//            4     4        (object header)                           54 02 00 00 (01010100 00000010 00000000 00000000) (596)
//            8     4        (object header)                           00 10 00 00 (00000000 00010000 00000000 00000000) (4096)
//            12     4        (loss due to the next object alignment)
//    Instance size: 16 bytes
//    Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
}

Lock Record以及ObjectMonitor存储的内容

image.png

2.5 重量级锁底层ObjectMonitor

需要去找到openjdk,在百度中直接搜索openjdk即可

找到ObjectMonitor的两个文件,hpp,cpp

先查看核心属性:https://hg.openjdk.org/jdk8u/jdk8u/hotspot/file/69087d08d473/src/share/vm/runtime/objectMonitor.hpp

  ObjectMonitor() {
    _header       = NULL;  // header存储着MarkWord
    _count        = 0;     // 竞争锁的线程个数
    _waiters      = 0,     // wait的线程个数
    _recursions   = 0;     // 标识当前synchronized锁重入的次数
    _object       = NULL;  
    _owner        = NULL;  // 持有锁的线程
    _WaitSet      = NULL;  // 保存wait的线程信息,双向链表
    _WaitSetLock  = 0 ;
    _Responsible  = NULL ;
    _succ         = NULL ;
    _cxq          = NULL ;  // 获取锁资源失败后,线程要放到当前的单向链表中
    FreeNext      = NULL ;
    _EntryList    = NULL ;  // _cxq以及被唤醒的WaitSet中的线程,在一定机制下,会放到EntryList中
    _SpinFreq     = 0 ;
    _SpinClock    = 0 ;
    OwnerIsThread = 0 ;
    _previous_owner_tid = 0;
  }

简单查看几个C++中实现的加锁流程

TryLock

int ObjectMonitor::TryLock (Thread * Self) {
   for (;;) {
	  // 拿到持有锁的线程
      void * own = _owner ;
	  // 如果有线程持有锁,结束
      if (own != NULL) return 0 ;
	  // 说明没有线程持有锁,own是null,cmpxchg指令就是底层的CAS实现
      if (Atomic::cmpxchg_ptr (Self, &_owner, NULL) == NULL) {
         // 成功获取锁资源
         return 1 ;
      }
    // 重试没有多大意义,因为锁刚刚被获取,返回-1
      if (true) return -1 ;
   }
}

try_enter

bool ObjectMonitor::try_enter(Thread* THREAD) {
   // 在判断_owner是不是当前线程
  if (THREAD != _owner) {
    // 判断当前持有锁的线程是否是当前线程,说明轻量级锁刚刚升级过来的情况
    if (THREAD->is_lock_owned ((address)_owner)) {
       _owner = THREAD ;
       _recursions = 1 ;
       OwnerIsThread = 1 ;
       return true;
    }
	// CAS操作,尝试获取锁资源
    if (Atomic::cmpxchg_ptr (THREAD, &_owner, NULL) != NULL) {
	// 没拿到锁资源,结束
      return false;
    }
    // 拿到锁资源
    return true;
  } else {
    // _recursions+1,代表锁重入操作
    _recursions++;
    return true;
  }
}

enter(想方设法拿到锁资源,如果没拿到,挂起扔到_cxq单向链表中)

void ATTR ObjectMonitor::enter(TRAPS) {
  // 拿到当前线程
  Thread * const Self = THREAD ;
  void * cur ;
  // cas尝试获取到锁
  cur = Atomic::cmpxchg_ptr (Self, &_owner, NULL) ;
  if (cur == NULL) {
   	 // 获取锁成功
     return ;
  }
 // 锁重入操作
  if (cur == Self) {
     // TODO-FIXME: check for integer overflow!  BUGID 6557169.
     _recursions ++ ;
     return ;
  }
  // 轻量级锁
  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 ;
    return ;
  }

  // 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) {
     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.
  // 走到这里,没拿到锁资源,count++
  Atomic::inc_ptr(&_count);

  JFR_ONLY(JfrConditionalFlushWithStacktrace<EventJavaMonitorEnter> flush(jt);)
  EventJavaMonitorEnter event;
  if (event.should_commit()) {
    event.set_monitorClass(((oop)this->object())->klass());
    event.set_address((uintptr_t)(this->object_addr()));
  }

  { // 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()
	  // 入队操作,进到cxq中
      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.
  }
  // count--
  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") ;

  // The thread -- now the owner -- is back in vm mode.
  // Report the glorious news via TI,DTrace and jvmstat.
  // The probe effect is non-trivial.  All the reportage occurs
  // while we hold the monitor, increasing the length of the critical
  // section.  Amdahl's parallel speedup law comes vividly into play.
  //
  // Another option might be to aggregate the events (thread local or
  // per-monitor aggregation) and defer reporting until a more opportune
  // time -- such as next time some thread encounters contention but has
  // yet to acquire the lock.  While spinning that thread could
  // spinning we could increment JVMStat counters, etc.

  DTRACE_MONITOR_PROBE(contended__entered, this, object(), jt);
  if (JvmtiExport::should_post_monitor_contended_entered()) {
    JvmtiExport::post_monitor_contended_entered(jt, this);

    // The current thread already owns the monitor and is not going to
    // call park() for the remainder of the monitor enter protocol. So
    // it doesn't matter if the JVMTI_EVENT_MONITOR_CONTENDED_ENTERED
    // event handler consumed an unpark() issued by the thread that
    // just exited the monitor.
  }

  if (event.should_commit()) {
    event.set_previousOwner((uintptr_t)_previous_owner_tid);
    event.commit();
  }

  if (ObjectMonitor::_sync_ContendedLockAttempts != NULL) {
     ObjectMonitor::_sync_ContendedLockAttempts->inc() ;
  }
}

EnterL

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 node(Self) ;
    Self->_ParkEvent->reset() ;
    node._prev   = (ObjectWaiter *) 0xBAD ;
    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 (;;) {
	// 入队
        node._next = nxt = _cxq ;
        // 以CAS方式入队
        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 ;
        }
    }

    // Check for cxq|EntryList edge transition to non-null.  This indicates
    // the onset of contention.  While contention persists exiting threads
    // will use a ST:MEMBAR:LD 1-1 exit protocol.  When contention abates exit
    // operations revert to the faster 1-0 mode.  This enter operation may interleave
    // (race) a concurrent 1-0 exit operation, resulting in stranding, so we
    // arrange for one of the contending thread to use a timed park() operations
    // to detect and recover from the race.  (Stranding is form of progress failure
    // where the monitor is unlocked but all the contending threads remain parked).
    // That is, at least one of the contended threads will periodically poll _owner.
    // One of the contending threads will become the designated "Responsible" thread.
    // The Responsible thread uses a timed park instead of a normal indefinite park
    // operation -- it periodically wakes and checks for and recovers from potential
    // strandings admitted by 1-0 exit operations.   We need at most one Responsible
    // thread per-monitor at any given moment.  Only threads on cxq|EntryList may
    // be responsible for a monitor.
    //
    // Currently, one of the contended threads takes on the added role of "Responsible".
    // A viable alternative would be to use a dedicated "stranding checker" thread
    // that periodically iterated over all the threads (or active monitors) and unparked
    // successors where there was risk of stranding.  This would help eliminate the
    // timer scalability issues we see on some platforms as we'd only have one thread
    // -- the checker -- parked on a timer.

    if ((SyncFlags & 16) == 0 && nxt == NULL && _EntryList == NULL) {
        // Try to assume the role of responsible thread for the monitor.
        // CONSIDER:  ST vs CAS vs { if (Responsible==null) Responsible=Self }
        Atomic::cmpxchg_ptr (Self, &_Responsible, NULL) ;
    }

    // The lock have been released while this thread was occupied queueing
    // itself onto _cxq.  To close the race and avoid "stranding" and
    // progress-liveness failure we must resample-retry _owner before parking.
    // Note the Dekker/Lamport duality: ST cxq; MEMBAR; LD Owner.
    // In this case the ST-MEMBAR is accomplished with CAS().
    //
    // TODO: Defer all thread state transitions until park-time.
    // Since state transitions are heavy and inefficient we'd like
    // to defer the state transitions until absolutely necessary,
    // and in doing so avoid some transitions ...

    TEVENT (Inflated enter - Contention) ;
    int nWakeups = 0 ;
    int RecheckInterval = 1 ;

    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() ;
    }

    // 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) ;
    if (_succ == Self) _succ = NULL ;

    assert (_succ != Self, "invariant") ;
    if (_Responsible == Self) {
        _Responsible = NULL ;
        OrderAccess::fence(); // Dekker pivot-point

        // We may leave threads on cxq|EntryList without a designated
        // "Responsible" thread.  This is benign.  When this thread subsequently
        // exits the monitor it can "see" such preexisting "old" threads --
        // threads that arrived on the cxq|EntryList before the fence, above --
        // by LDing cxq|EntryList.  Newly arrived threads -- that is, threads
        // that arrive on cxq after the ST:MEMBAR, above -- will set Responsible
        // non-null and elect a new "Responsible" timer thread.
        //
        // This thread executes:
        //    ST Responsible=null; MEMBAR    (in enter epilog - here)
        //    LD cxq|EntryList               (in subsequent exit)
        //
        // Entering threads in the slow/contended path execute:
        //    ST cxq=nonnull; MEMBAR; LD Responsible (in enter prolog)
        //    The (ST cxq; MEMBAR) is accomplished with CAS().
        //
        // The MEMBAR, above, prevents the LD of cxq|EntryList in the subsequent
        // exit operation from floating above the ST Responsible=null.
    }

    // We've acquired ownership with CAS().
    // CAS is serializing -- it has MEMBAR/FENCE-equivalent semantics.
    // But since the CAS() this thread may have also stored into _succ,
    // EntryList, cxq or Responsible.  These meta-data updates must be
    // visible __before this thread subsequently drops the lock.
    // Consider what could occur if we didn't enforce this constraint --
    // STs to monitor meta-data and user-data could reorder with (become
    // visible after) the ST in exit that drops ownership of the lock.
    // Some other thread could then acquire the lock, but observe inconsistent
    // or old monitor meta-data and heap data.  That violates the JMM.
    // To that end, the 1-0 exit() operation must have at least STST|LDST
    // "release" barrier semantics.  Specifically, there must be at least a
    // STST|LDST barrier in exit() before the ST of null into _owner that drops
    // the lock.   The barrier ensures that changes to monitor meta-data and data
    // protected by the lock will be visible before we release the lock, and
    // therefore before some other thread (CPU) has a chance to acquire the lock.
    // See also: http://gee.cs.oswego.edu/dl/jmm/cookbook.html.
    //
    // Critically, any prior STs to _succ or EntryList must be visible before
    // the ST of null into _owner in the *subsequent* (following) corresponding
    // monitorexit.  Recall too, that in 1-0 mode monitorexit does not necessarily
    // execute a serializing instruction.

    if (SyncFlags & 8) {
       OrderAccess::fence() ;
    }
    return ;
}

三、深入ReentrantLock

3.1 ReentrantLock和synchronized的区别

核心区别:

  • ReentrantLock是个类,synchronized是关键字,当然都是在JVM层面实现互斥锁的方式

效率区别:

  • 如果竞争比较激烈,推荐ReentrantLock去实现,不存在锁升级概念,而synchronized是存在锁升级概念的,如果升级到重量级锁,是不存在锁降级的。

底层实现的区别:

  • 实现原理不一样,ReentrantLock基于AQS实现的,synchronized是基于ObjectMoniter

功能项区别:

  • ReentrantLock的功能比synchronized更全面
    • ReentrantLock支持公平锁和非公平锁
    • ReentrantLock可以指定等待锁资源的时间

选择那个,如果是对并发编程特别熟练,推荐使用ReentrantLock,功能更丰富,如果掌握的一般般,使用synchronized更好

3.2 AQS概述

AQS就是AbstractQueuedSynchronizer抽象类,AQS其实就是JUC包下的一个基类,JUC下的很多内容都是基于AQS实现了部分功能,比如ReentrantLock,ThreadPoolExecutor,阻塞队列,CountDownLatch,Semaphore,CyclicBarrier等等都是基于AQS实现

首先AQS中提供了一个由volatile修饰,并且用CAS方式修饰的int类型的state变量

其次AQS中维护了一个双向链表,有head,有tail,,并且每个节点都是Node对象

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled. */
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking. */
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition. */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate.
         */
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         *
         * @return the predecessor of this node
         */
        final Node predecessor() {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        /** Establishes initial head or SHARED marker. */
        Node() {}

        /** Constructor used by addWaiter. */
        Node(Node nextWaiter) {
            this.nextWaiter = nextWaiter;
            THREAD.set(this, Thread.currentThread());
        }

        /** Constructor used by addConditionWaiter. */
        Node(int waitStatus) {
            WAITSTATUS.set(this, waitStatus);
            THREAD.set(this, Thread.currentThread());
        }

        /** CASes waitStatus field. */
        final boolean compareAndSetWaitStatus(int expect, int update) {
            return WAITSTATUS.compareAndSet(this, expect, update);
        }

        /** CASes next field. */
        final boolean compareAndSetNext(Node expect, Node update) {
            return NEXT.compareAndSet(this, expect, update);
        }

        final void setPrevRelaxed(Node p) {
            PREV.set(this, p);
        }

        // VarHandle mechanics
        private static final VarHandle NEXT;
        private static final VarHandle PREV;
        private static final VarHandle THREAD;
        private static final VarHandle WAITSTATUS;
        static {
            try {
                MethodHandles.Lookup l = MethodHandles.lookup();
                NEXT = l.findVarHandle(Node.class, "next", Node.class);
                PREV = l.findVarHandle(Node.class, "prev", Node.class);
                THREAD = l.findVarHandle(Node.class, "thread", Thread.class);
                WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);
            } catch (ReflectiveOperationException e) {
                throw new ExceptionInInitializerError(e);
            }
        }
    }

AQS内部结构和属性

image.png

3.3 加锁流程源码解析

3.3.1 加锁流程图

image.png

  1. 线程A先执行CAS,将state从0修改为1,线程A就获取到了锁资源,去执行业务代码即可
  2. 线程B在执行CAS,发现state已经是1了,无法获取到锁资源
  3. 线程B需要去排序,将自己封装为Node对象
  4. 需要将当前B线程的Node放到双向队列中保存,排队
    1. 但是双向链表中,必须先有个伪节点作为头节点,并且放到双向队列中
    2. 将B线程的Node挂在head的后面,并且将上一个节点的状态修改为-1,在挂起B线程

3.3.2 源码分析

3.3.2.1 lock方法
  1. 执行lock方法后,公平锁和非公平锁的执行逻辑不一样

    • 非公平锁

      // 非公平锁
      final void lock() {
          // 一来就先基于CAS的方式,尝试将state从0改为1
          if (compareAndSetState(0, 1))
              // 获取锁成功了,会将当前线程设置到exclusiveOwnerThread属性,代表当前线程持有者锁资源
              setExclusiveOwnerThread(Thread.currentThread());
          else
      	// 执行acquire,尝试获取锁资源
              acquire(1);
      }
      
    • 公平锁

      // 公平锁
      final void lock() {
          // 执行acquire,尝试获取锁资源
          acquire(1);
      }
      
  2. acquire方法,公平锁和非公平锁的逻辑一样

    public final void acquire(int arg) {
        // tryAcquire:再次查看,当前线程是否可以尝试获取锁资源
        if (!tryAcquire(arg) &&
           // 没有拿到锁资源
           // addWaiter(Node.EXCLUSIVE):将当前线程封装为NOde节点,插入到AQS双向链表的结尾
           // acquireQueued:查看我是否第一个排队的节点,如果可以再次尝试获取锁资源,如果长时间拿不到,挂起线程
           // 如果不是第一个排队节点,就尝试挂起线程即可
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
           // 中断线程的操作
            selfInterrupt();
    }
    
  3. tryAcuire方法竞争锁资源的逻辑,分为了公平锁和非公平锁

    • 非公平锁

      final boolean nonfairTryAcquire(int acquires) {
          // 获取当前线程
          final Thread current = Thread.currentThread();
          // 获取state属性
          int c = getState();
          // 判断state当前是否为0,之前持有锁线程是否释放了锁资源
          if (c == 0) {
      	// 再次抢一波锁资源
              if (compareAndSetState(0, acquires)) {
                  setExclusiveOwnerThread(current);
      	    // 获取锁成功返回true
                  return true;
              }
          }
         // 不是0,判断当前线程是否是持有锁线程,如果是,锁重入
          else if (current == getExclusiveOwnerThread()) {
      	// 将state+1
              int nextc = c + acquires;
              if (nextc < 0) // overflow
      	// 说明对重入次数+1后,超过了int正数的取值范围
      	// 01111111 11111111 11111111 11111111
      	// 10000000 00000000 00000000 00000000
      	// 最高位是1代表负数,0代表正数
      	// 说明重入的次数超过界限了
                  throw new Error("Maximum lock count exceeded");
      	// 正常的将计算结果,赋值给state
              setState(nextc);
      	// 锁重入成功
              return true;
          }
          // 获取锁失败,返回false
          return false;
      }
      
    • 公平锁

      protected final boolean tryAcquire(int acquires) {
          // 获取当前线程
          final Thread current = Thread.currentThread();
          // 获取state属性
          int c = getState();
      	// 判断state当前是否为0,之前持有锁线程是否释放了锁资源
          if (c == 0) {
      		// 查看AQS中是否有排队的Node
      		// 没有排队抢一手,有人排队,如果我是第一个,也抢一手
              if (!hasQueuedPredecessors() &&
      			// 尝试将state修改成 1
                  compareAndSetState(0, acquires)) {
                  setExclusiveOwnerThread(current);
                  return true;
              }
          }
      	// 锁重入
          else if (current == getExclusiveOwnerThread()) {
              int nextc = c + acquires;
              if (nextc < 0)
                  throw new Error("Maximum lock count exceeded");
              setState(nextc);
              return true;
          }
      	// 获取锁失败,返回false
          return false;
      }
      
      • hasQueuedPredecessors() 查看是否有线程在AQS中排队

        public final boolean hasQueuedPredecessors() {
            // 尾节点
            Node t = tail;
            // 头节点
            Node h = head;
            Node s;
           // 条件1 h != t
           // 当h == t,则 h!=t 返回false
           // 条件2 ((s = h.next) == null || s.thread != Thread.currentThread())
           //  s 为头节点下一个节点 
           //  s 节点不为null,并且是节点的线程为当前线程(排在第一名的是不是我)
           // 当 头节点下一个节点的线程 == 当前线程,则 s.thread != Thread.currentThread() 返回false
            return h != t &&
                ((s = h.next) == null || s.thread != Thread.currentThread());
        }
        
  4. addWaite方法,将没有拿到锁资源的线程扔到AQS队列中去排队

    // 没有拿到锁资源,进行排队,mode:EXCLUSIVE 互斥锁 SHARED:共享锁
    private Node addWaiter(Node mode) {
        // 将当前线程封装成Node
        Node node = new Node(Thread.currentThread(), mode);
        // 拿到尾节点
        Node pred = tail;
        // 尾节点不为空
        if (pred != null) {
            // 当前的前节点指向尾节点
            node.prev = pred;
    	// 以CAS的方式,将当前线程设置为tail节点
            if (compareAndSetTail(pred, node)) {
    	    // 将之前的尾节点的next指向当前节点
    	    // node当前节点就是尾节点
                pred.next = node;
                return node;
            }
        }
         // 如果CAS失败,以死循环的方式,保证当前线程的Node一定可以放到AQS的队列的末尾
        enq(node);
        return node;
    }
    
    • enq(node) 保证当前线程的Node一定可以放到AQS的队列的末尾

      private Node enq(final Node node) {
          for (;;) {
      	// 获取尾节点
              Node t = tail;
      	// 尾节点为空,AQS总一个节点都没有,构建一个伪节点,作为head和tail
              if (t == null) { // Must initialize
      	    // 构建伪节点作为head和tail
                  if (compareAndSetHead(new Node()))
                      tail = head;
              } else {
      	    // 当前节点的前指针指向尾巴节点
                  node.prev = t;
      	    // 当前节点作为尾节点
                  if (compareAndSetTail(t, node)) {
      		// 将之前的尾节点的next指向当前节点
      	        // node当前节点就是尾节点
                      t.next = node;
                      return t;
                  }
              }
          }
      }
      
  5. acquireQueued方法,判断当前线程是否还能再次尝试获取锁资源,如果不能获取锁资源,或者又没有获取到,尝试将当前线程挂起

    // 当前没有拿到锁资源后,并且到了AQS排队了之后触发的方法
    final boolean acquireQueued(final Node node, int arg) {
       //  failed: 获取锁资源是否失败(真正触发的,还是tryLock和lockInterruptibly)
        boolean failed = true;
        try {
    	// 中断
            boolean interrupted = false;
            for (;;) {
    	   // 拿到当前节点的前继节点
                final Node p = node.predecessor();
    	    // 前继节点是否是头节点,如果是,再次执行tryAcquire尝试获取锁
                if (p == head && tryAcquire(arg)) {
    		// 获取锁资源成功
    		// 设置头节点为当前拿锁成功的Node,并取消thread信息
                    setHead(node);
                    p.next = null; // help GC
    		// 设置锁失败标识为false
                    failed = false;
                    return interrupted;
                }
    	    // 没拿到锁资源
    	    // shouldParkAfterFailedAcquire:基于上一个节点状态判断当前节点是否能够挂起线程,如果可以返回true
    	    // 如果不能,就返回false,继续下次循环
                if (shouldParkAfterFailedAcquire(p, node) &&
    		// 这里基于Unsafe类的park方法,将当前线程挂起
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
    	     // 在lock方法中,基本不会执行
                cancelAcquire(node);
        }
    
    • setHead(),获取锁成功之后,先执行setHead

      // 获取锁成功之后,先执行setHead
      private void setHead(Node node) {
          // 当前节点作为头
          head = node;
          // 头节点不需要线程信息
          node.thread = null;
          node.prev = null;
      }
      
    • shouldParkAfterFailedAcquire 当前Node没有拿到锁资源,或者没有资格竞争锁资源,看一下能否挂起当前线程

      // 当前Node没有拿到锁资源,或者没有资格竞争锁资源,看一下能否挂起当前线程
      private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
           // -1,SIGNAL状态: 代表前节点的后继节点,可以挂起线程,后续我会被唤醒后继节点
           // 1,CANCELLED状态: 代表当前节点取消
           // 前继节点的状态
          int ws = pred.waitStatus;
          if (ws == Node.SIGNAL)
              // 前继节点的状态为-1,当前节点可以安心挂起线程
              return true;
          if (ws > 0) {
              // 前继节点是取消的状态,我需要往前找到一个状态不为1的Node,作为他的next节点
              do {
      	    // node的前继节点是前继节点
                  node.prev = pred = pred.prev;
              } while (pred.waitStatus > 0);
      	// pread节点不为1,设置一下next节点
      	// node作为当前pread节点的next节点
              pred.next = node;
          } else {
               // 前继节点不是1和-1,那就是代表节点状态正常,将前继节点状态为-1
              compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
          }
          return false;
      }
      
3.3.2.2 tryLock方法
  • boolean tryLock(); 无论是公平锁还是非公平锁,都会走非公平锁抢占锁资源的操作,拿到state的值,如果是0,直接CAS尝试一下,state不是0,那就看一下是不是锁重入操作,如果没抢到或者不是锁重入操作,结束
    • tryLock()

      public boolean tryLock() {
          // 非公平锁的竞争锁操作
          return sync.nonfairTryAcquire(1);
      }
      
    • nonfairTryAcquire(1)

      final boolean nonfairTryAcquire(int acquires) {
          // 获取当前线程
          final Thread current = Thread.currentThread();
          // 获取state属性
          int c = getState();
          // 判断state当前是否为0,之前持有锁线程是否释放了锁资源
          if (c == 0) {
      	// 再次抢一波锁资源
              if (compareAndSetState(0, acquires)) {
                  setExclusiveOwnerThread(current);
      	    // 获取锁成功返回true
                  return true;
              }
          }
         // 不是0,判断当前线程是否是持有锁线程,如果是,锁重入
          else if (current == getExclusiveOwnerThread()) {
      	// 将state+1
              int nextc = c + acquires;
              if (nextc < 0) // overflow
      	// 说明对重入次数+1后,超过了int正数的取值范围
      	// 01111111 11111111 11111111 11111111
      	// 10000000 00000000 00000000 00000000
      	// 最高位是1代表负数,0代表正数
      	// 说明重入的次数超过界限了
                  throw new Error("Maximum lock count exceeded");
      	// 正常的将计算结果,赋值给state
              setState(nextc);
      	// 锁重入成功
              return true;
          }
          // 获取锁失败,返回false
          return false;
      }
      
      
  • boolean tryLock(long time, TimeUnit unit); 指定获取锁等待时间,这个时间内没有获取锁成功,就结束
    • tryLock((long time, TimeUnit unit);

      public boolean tryLock(long timeout, TimeUnit unit)
              throws InterruptedException {
          // unit.toNanos(timeout): 转换为纳秒
          return sync.tryAcquireNanos(1, unit.toNanos(timeout));
      }
      
    • boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException

      public final boolean tryAcquireNanos(int arg, long nanosTimeout)
              throws InterruptedException {
          // 线程的中继标记位,是不是从false,改为了true,如果是,直接抛出异常
          if (Thread.interrupted())
              throw new InterruptedException();
          // tryAcquire 分为公平锁和非公平锁两种执行方式,如果拿锁成功,结束
          // doAcquireNanos,如果拿锁失败了,在这里等待指定时间
          return tryAcquire(arg) ||
              doAcquireNanos(arg, nanosTimeout);
      }
      
    • doAcquireNanos(int arg, long nanosTimeout)

      private boolean doAcquireNanos(int arg, long nanosTimeout)
              throws InterruptedException {
          // 如果等待时间是0秒,结束,拿锁失败
          if (nanosTimeout <= 0L)
              return false;
          // 计算出结束时间,当前时间+设置的时间
          final long deadline = System.nanoTime() + nanosTimeout;
          // 先放到AQS队列中
          final Node node = addWaiter(Node.EXCLUSIVE);
          // 拿锁失败,默认标识true
          boolean failed = true;
          try {
              for (;;) {
      	    // 获取当前节点的前继节点
                  final Node p = node.predecessor();
      	    // 获取当前节点的前继节点是头节点,直接抢锁
                  if (p == head && tryAcquire(arg)) {
      	        // 抢锁成功设置,头节点
                      setHead(node);
                      p.next = null; // help GC
                      failed = false;
                      return true;
                  }
      	    // 计算出剩余可以的时间
                  nanosTimeout = deadline - System.nanoTime();
      	    // 判断是否还有时间
                  if (nanosTimeout <= 0L)
                      return false;
      	   // shouldParkAfterFailedAcquire:更具前继节点状态判断是否可以挂起线程
                  if (shouldParkAfterFailedAcquire(p, node) &&
      	     // spinForTimeoutThreshold = 1000L
      	     // 避免剩余时间太少,如果剩余时间少就不用挂起线程
                      nanosTimeout > spinForTimeoutThreshold)
      		// 时间足够,将线程挂起剩余时间
                      LockSupport.parkNanos(this, nanosTimeout);
      	    // 如果线程醒了,查看是中继唤醒,还是时间到了
                  if (Thread.interrupted())
      		// 是中继唤醒
                      throw new InterruptedException();
              }
          } finally {
              if (failed)
                  cancelAcquire(node);
          }
      }
      
    • cancelAcquire(Node node) 取消节点

      image.png

      1. 线程设置为NULL
      2. 往前找到有效节点作为当前节点的prev
      3. 将waitStatus设置为1,代表取消
      4. 脱离整个AQS队列
        1. 当前Node是tail
        2. 当地Node是head的后继节点
        3. 不是tail节点,也不是head的后继节点
      // 取消在AQS中排队的Node
      private void cancelAcquire(Node node) {
          // 如果当前节点为null,直接忽略
          if (node == null)
              return;
          // 1. 线程设置为NULL
          node.thread = null;
      
          // 2. 往前跳过被取消的节点,
          Node pred = node.prev;
          // 找到下一个有效的节点
          while (pred.waitStatus > 0)
              node.prev = pred = pred.prev;
          // 3.拿到有效节点的next
          Node predNext = pred.next;
          // 当前节点设置取消状态1
          node.waitStatus = Node.CANCELLED;
          // 脱离AQS队列的操作
          // 当前Node节点是尾节点,将找到的有效节点设置为tail
          if (node == tail && compareAndSetTail(node, pred)) {
              compareAndSetNext(pred, predNext, null);
          } else {
              // 走到这说明node不是尾节点,或者CAS失败
              int ws;
      	// pred != head 判断找到的有效节点是不是头节点
      	// (ws = pred.waitStatus) == Node.SIGNAL ||
      	// (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))
      	//  拿到有效节点的状态,只要有效节点状态不是取消状态,就会改为-1
      	//  pred.thread != null 判断有效节点线程不为null
              if (pred != head &&
                  ((ws = pred.waitStatus) == Node.SIGNAL ||
                   (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                  pred.thread != null) {
      	    // 上面的判断都是为了避免后面的节点无法被唤醒
      	    // 前继节点是有效节点,可以唤醒后面的节点
      	    // 获取取消节点的后继节点
                  Node next = node.next;
      	    // 判断后继节点是个正常的节点,不是取消的节点
                  if (next != null && next.waitStatus <= 0)
      		// 设置有效节点的后继节点
                      compareAndSetNext(pred, predNext, next);
              } else {
      	    // 当前节点是head的后继节点
                  unparkSuccessor(node);
              }
      
              node.next = node; // help GC
          }
      }
      
      
    • unparkSuccessor(Node node), 取消节点是head后继节点,并唤醒有效节点

      private void unparkSuccessor(Node node) {
          // 获取当前节点状态
          int ws = node.waitStatus;
          // 当前节点状态设置为0态
          if (ws < 0)
              compareAndSetWaitStatus(node, ws, 0);
          // 取消节点中的后继节点如果被取消或者线程明显为空
          // 则从尾部向前遍历找到有效的节点
          Node s = node.next;
          if (s == null || s.waitStatus > 0) {
              s = null;
              for (Node t = tail; t != null && t != node; t = t.prev)
                  if (t.waitStatus <= 0)
                      s = t;
          }
          // 有效节点不为空,唤醒线程
          if (s != null)
              LockSupport.unpark(s.thread);
      }
      
3.3.2.3 lockInterruptibly()

lockInterruptibly()和tryLock(time,unit)唯一区别

lockInterruptibly(),拿不到锁资源,就死等,等到锁资源释放后,被唤醒,或者被中继唤醒

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
	// 封装为Node对象添加AQS队列中
    final Node node = addWaiter(Node.EXCLUSIVE);
	// 获取锁失败标识
    boolean failed = true;
    try {
        for (;;) {
			// 获取前继节点
            final Node p = node.predecessor();
			// 前继节点是否头节点
			// 是头节点尝试获取锁
            if (p == head && tryAcquire(arg)) {
				// 设置头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
			// shouldParkAfterFailedAcquire:基于上一个节点状态判断当前节点是否能够挂起线程,如果可以返回true
			// parkAndCheckInterrupt:判断是中断唤醒还是,正常唤醒
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

parkAndCheckInterrupt

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
	// 这个方法可以确认,当前挂起的线程,是被中继唤醒的,还是被正常唤醒的
	// 中继唤醒,返回true,如果正常唤醒,返回false
    return Thread.interrupted();
}

3.4 释放锁的流程

3.4.1 释放锁流程分析

image.png

线程A持有当前锁,重入了一次,state = 2,线程B,C,D获取锁资源失败,在AQS中排队

线程A释放锁资源调用unlock方法,就是执行了tryRelease方法

首先判断是不是线程A持有锁资源,如果不是就抛出异常

如果线程A持有锁资源,对state -1,如果为0,证明当前锁资源释放干净

查看头节点的状态,如果是0说明后面没有挂起的线程,是-1后面有挂起的线程,需要唤醒需要先将当前的-1,改为0,找到有效节点唤醒,找到之后,唤醒线程即可,如果头节点的后继节点是取消的状态,则会从链表尾巴从前找到有效节点

为什么要从链表尾部开始找有效节点呢?
因为要防止漏掉要唤醒的挂起的线程,节点插入链表是先prev指针先指向尾节点的,在,next指针,和tail指定当前插入节点,如果从头部开始找的可能会漏掉有效节点,当next指针还没有插入节点的时候

3.4.2 释放锁源码分析

  • unlock()
public void unlock() {
    // 释放锁资源不分为公平锁和非公平锁,都是一个sync对象
    sync.release(1);
}
  • boolean release(int arg)
// 释放锁的核心流程
public final boolean release(int arg) {
    // 核心释放锁资源,对state-1操作
    if (tryRelease(arg)) {
	// 如果锁释放干净了,走这个逻辑
	// 获取到头节点
        Node h = head;
	// 头节点不为null
	// 如果头节点状态不为0(为-1),说明后面有点排队的Node,并且线程已经挂起了
        if (h != null && h.waitStatus != 0)
	     // 唤醒排队的线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}
  • boolean tryRelease(int releases)
// 释放锁资源的操作
protected final boolean tryRelease(int releases) {
    // 拿到state - 1(并没有赋值给state)
    int c = getState() - releases;
    // 判断当前线程是否是持有锁线程,如果不是,直接抛出异常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 标识锁是否是否干净标志
    boolean free = false;
    if (c == 0) {
	// 如果state -1 等于0,说明锁释放干净了
        free = true;
	// 将持有锁线程设置为null
        setExclusiveOwnerThread(null);
    }
    // 将c赋值给state
    setState(c);
   // 锁资源释放干净返回true,否则返回false
    return free;
}
  • unparkSuccessor(Node node) 唤醒有效节点
// 当前节点是head
private void unparkSuccessor(Node node) {
    // 获取当前节点状态
    int ws = node.waitStatus;
    // 当前节点状态设置为0态
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 取消节点中的后继节点如果被取消或者线程明显为空
    // 则从尾部向前遍历找到有效的节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 有效节点不为空,唤醒线程
    if (s != null)
        LockSupport.unpark(s.thread);
}

  • 23
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值