ReentrantLock的公平锁源码分析

AbstractQueuedSynchronizer#acquire方法是获得锁的入口方法,其主要的设计理念就是使用乐观锁的方式设置 state 的属性值,如果设置失败或者当前有线程持有锁,那么为当前线程创建一个节点并添加到双链表中。下面先分析该方法。

AbstractQueuedSynchronizer#acquire

/**
     * 尝试获取锁
     * Acquires in exclusive mode, ignoring interrupts.  Implemented
     * by invoking at least once {@link #tryAcquire},
     * returning on success.  Otherwise the thread is queued, possibly
     * repeatedly blocking and unblocking, invoking {@link
     * #tryAcquire} until success.  This method can be used
     * to implement method {@link Lock#lock}.
     *
     * @param arg the acquire argument.  This value is conveyed to
     *        {@link #tryAcquire} but is otherwise uninterpreted and
     *        can represent anything you like.
     */
    public final void acquire(int arg) {
          // 通过调用 tryAcquire 方法尝试设置 state 属性值,如果返回 true,表示持有锁,那么该方法结束
          // addWaiter 方法将为当前线程创建一个节点,并添加到链表的尾部,使用的是乐观锁的方式,如果链表没有初始化,那么会先初始化
          // 调用 acquireQueued 方法尝试使用自选锁的方式获得锁,牺牲 CPU 避免调用 LockSupport#park 方法
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

AbstractQueuedSynchronizer#acquire 方法步骤主要为:

  1. 尝试通过 tryAcquire 方法设置 state 的值,使用的是乐观锁的方式。
    1. 由于是公平锁,所以如果此时恰巧有线程释放了锁,那么会判断此时双链表中是否有节点在等待锁(hasQueuedPredecessors方法,该方法实现很有意思,后面会单独分析代码),若有或乐观锁设置 state 变量值失败,那么直接返回 false;
    2. 判断当前线程是否为持有锁的线程,若是,则返回 true;
    3. 返回 false;
  2. 调用 addWaiter 方法为当前线程创建一个节点,并添加到链表尾部,同样使用的是乐观锁设置值。
  3. 调用 acquireQueued 方法尝试使用自选锁的方式获得锁,牺牲 CPU 避免调用 LockSupport#park 方法。
    1. 判断自己是否为链表中的第一个等待节点,如果是,那么再次调用 tryAcquire 方法(第一次自旋)尝试获得锁,若获得锁成功,将 head 指向当前节点,为了 GC,将原 head.next 置为 null,该方法结束返回 false。
    2. 如果调用 tryAcquire 方法失败,那么调用 shouldParkAfterFailedAcquire 方法根据上一个节点的状态(waitStatus == -1)判断是否需要将线程挂起,由于这个属性的初始值为 0,所以此时调用该方法会将上一个节点的 waitStatus 设置为 -1 并返回 false,由于该方法是一个死循环,所以重新回调上一步(第二次自旋)。
    3. 如果 shouldParkAfterFailedAcquire 方法返回 true,表示那么会调用 parkAndCheckInterrupt 方法将线程挂起(通过 LockSupport#park 方法,后面会分析),该方法返回 true 表示当前线程被中断,返回 false 表示没有被中断,如果返回 true 会调用 selfInterrupt 方法完成中断。

        总结 tryAcquire 方法返回 true 的情况就是 state 为 0,并且没有等待的节点,并且使用乐观锁更新 state 值成功;或者当前线程是持有锁的线程。

可以打断被 park 方法挂起的线程的方式:

  1. LockSupport#unpark(Thread)
  2. Thread#interrupt

其实AQS的实现原理就是双链表+乐观锁+自旋锁+LockSupport#park方法。

LockSupport#park

  1. 根据 C++ 代码中可知道,Parker对象中持有 _cond 属性(信号量),该属性决定了线程是否需要挂起(其实如果线程有中断标志也可以继续执行),如果 _cond > 0,那么表示不需要挂起,此时将 _cond 设置为 0。如果 _cond = 0,那么表示没有执行许可,此时会再判断是否有中断标志,如果有,那么 park 方法执行结束;如果 _cond = 0 又没有中断标志,那么将线程设置为阻塞状态,并挂起线程,直到有通知,会将 _cond 设置为 0,park 方法执行结束。总结步骤为:判断 _cond >  0 是否为 true,若为 true,那么表示有执行许可,将 _cond 设置为 0,park 方法执行结束返回。
  2. _cond > 0 为 false,判断是否有中断标志,若有,那么 park 方法执行结束。
  3. 如果 _cond > 0 为 false,并且没有中断标志,那么将 java线程所拥有的操作系统线程设置为 CONDVAR_WAIT 状态,等待某个条件的通知。
  4. 接收到通知后,将 _cond 设置为 0,park 方法执行结束。

下面是 Parker 对象的 C++ 代码:   

class PlatformParker : public CHeapObj {
      protected:
        //互斥变量类型
        pthread_mutex_t _mutex [1] ; 
       //条件变量类型
        pthread_cond_t  _cond  [1] ;
    
      public:        
         ~PlatformParker() { guarantee (0, "invariant") ; }
    
      public:
        PlatformParker() {
          int status;
         //初始化条件变量,使用    pthread_cond_t之前必须先执行初始化
          status = pthread_cond_init (_cond, NULL);
          assert_status(status == 0, status, "cond_init”);
          // 初始化互斥变量,使用    pthread_mutex_t之前必须先执行初始化
          status = pthread_mutex_init (_mutex, NULL);
          assert_status(status == 0, status, "mutex_init");
        }
    } 

LockSupport#unpark

调用 unpark 方法时,会将 _cond 设置为 1,唤醒线程让其继续执行。unpark 方法可以非常精准的唤醒一个线程,区别于 notify 是随机通知一个线程,而 notifyAll 则是通知所有在等待该条件的线程。

基于 park 方法和 unpark 方法的特性,我们完全可以先调用 unpark 方法,再执行 park 方法,此时不会挂起线程。

AbstractQueuedSynchronizer#release

release 方法是释放锁的入口方法,它的逻辑比较简单,就是通知双链表中第一个等待节点(head 节点是一个空节点),通知的方式就是靠 LockSupport#unpark 方法实现。

/**
     * Releases in exclusive mode.  Implemented by unblocking one or
     * more threads if {@link #tryRelease} returns true.
     * This method can be used to implement method {@link Lock#unlock}.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryRelease} but is otherwise uninterpreted and
     *        can represent anything you like.
     * @return the value returned from {@link #tryRelease}
     */
    public final boolean release(int arg) {
          // tryRelease 判断 state 属性值是否为 0
          // 可重入锁需要等待所有调用了 lock 的地方都调用 unlock 方法,即调用多少次 lock 方法,就需要对应多少次 unlock 方法。
        if (tryRelease(arg)) {
              // 通知双链表中第一个等待节点的线程
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

AbstractQueuedSynchronizer#hasQueuedPredecessors

不是必须了解的,只需要知道该方法是用于判断是否有在等待的节点即可。但是其设计非常的巧妙不得不感叹。

AbstractQueuedSynchronizer#hasQueuedPredecessors方法是公平锁用于判断当前是否有在其前面的节点。先看下将节点添加到双向链表的方法 AbstractQueuedSynchronizer#enq 方法   

/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    private Node enq(final Node node) {
        // 由于使用的是乐观锁,可能将该节点添加到尾节点失败,所以需要死循环
        for (;;) {
            // 当前的尾节点,如果别的线程设置成功了,那么乐观锁尝试设置尾节点会失败
            Node t = tail;
            // 如果为null,表示还没有初始化
            if (t == null) { // Must initialize
                // 乐观锁设置头节点,这是一个空节点
                // 初始化后头节点和尾节点相等
                // 进入第二个循环
                // 这里有一个间隙可能出现head和tail不相等的情况,表明此时有一个节点正在尝试加入队列
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                // 先尝试设置当前节点的上一个节点为尾节点
                node.prev = t;
                // 使用乐观锁将当前节点设置为尾节点
                // 如果失败,那么进入下一个循环
                if (compareAndSetTail(t, node)) {
                    // 成功设置当前节点为尾节点,那么将老的尾节点的next指针指向新的尾节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

下面看 hasQueuedPredecessors 方法返回值的情况:

  1. tail != head 返回 true
    1. tail == null 为 true,head == null 为 false,可能发生在双链表初始化的阶段,即已经创建了头节点,但是还没有把尾节点设置为头节点。
    2. tail == null 为 false,head == null 为 false,这种情况表示双链表中有等待的节点
  2. tail != head 返回 false
    1. tail == null 为 true,head == null 为 true,表明链表没有初始化
    2. tail == null 为 false,head == null 为 false,表明此时链表已经初始化了,相等表明没有在等待的节点
  3. tail != head 返回 true 并且 s = h.next == null  返回 true
    1. 就是1.1这种情况,有节点在尝试加入队列,但是还没加入
  4. tail != head 返回 true 并且 s = h.next == null 返回 false
    1. 表明此时有链表中有节点在等待,判断第一个节点的线程是不是当前线程
 // 判断其前面是否有节点,保证公平获得锁
    // 返回 true 表示前面有等待的节点,返回 false 表示前面没有等待的节点
    public final boolean hasQueuedPredecessors() {
        // 1、tail != head 返回 true
        //  1)tail == null 为 true,head == null 为 false,可能发生在双链表初始化的阶段,
        //    即已经创建了头节点,但是还没有把尾节点设置为头节点。
        //  2)tail == null 为 false,head == null 为 false,这种情况表示双链表中有等待的节点
        // 2、tail != head 返回 false
        //  1) tail == null 为 true,head == null 为 true,表明链表没有初始化
        // 2) tail == null 为 false,head == null 为 false,表明此时链表已经初始化了,相等表明没有在等待的节点
        // 3、tail != head 返回 true 并且 s = h.next == null  返回 true
        // 1)就是1的1)这种情况,有节点在尝试加入队列,但是还没加入
        // 4、 tail != head 返回 true 并且 s = h.next == null 返回 false
        // 1)表明此时有链表中有节点在等待,判断第一个节点的线程是不是当前线程
            
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

FairSync#tryAcquire   

/**
     * 尝试设置 state 属性的值为 acquires,若为可重入锁,那么 state += acquires
     * 如果修改状态成功
     * Fair version of tryAcquire.  Don't grant access unless
     * recursive call or no waiters or is first.
     */
    protected final boolean tryAcquire(int acquires) {
          // 获取当前线程
        final Thread current = Thread.currentThread();
          // 获取 state 变量的值
        int c = getState();
          // 如果为 0 表示没有线程持有锁
        if (c == 0) {
              // 调用 hasQueuedPredecessors 方法判断是否有节点尝试入队列,这种情况发生的情况为:持有锁的线程刚好释放了锁,由于是公平锁,所以需要判断前面是否有在等待的节点
              // 如果前面没有在等待的节点,那么认为当前节点可以竞争锁,尝试使用乐观锁将state的值设置为1,如果设置成功,那么持有锁,设置当前持有锁的线程为当前线程
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        else if (current == getExclusiveOwnerThread()) {// 判断当前线程是否为持有锁的线程,这里说明它是一个可重入锁
              // 持有锁数量+1
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }


 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值