ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

AQS 获取独占锁

2021-04-01 22:29:32  阅读:183  来源: 互联网

标签:node Node AQS 独占 pred util 获取 前驱 节点


  1. java.util.concurrent.locks.AbstractQueuedLongSynchronizer#acquire 在这个方法里
        public final void acquire(long arg) {
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }

     

  2. tryAcquire是抽象的,如果不知道为什么是抽象的可以关掉这个帖子了。
  3. 接下来看java.util.concurrent.locks.AbstractQueuedLongSynchronizer#addWaiter
        private Node addWaiter(Node mode) {
            Node node = new Node(Thread.currentThread(), mode);
            // Try the fast path of enq; backup to full enq on failure
            Node pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            enq(node);
            return node;
        }

     

    1. new一个节点,这些节点构成了AQS维护的FIFO队列,可以看到传进来的mode是独占的
    2. 然后获取到AQS的队尾,如果队尾是null,说明当前队列是空队列,通过 unsafe.compareAndSwapObject(this, tailOffset, expect, update); 原子操作将当前线程封装成的节点设置为队尾节点,如果设置成功,也就是当前线程的节点成功插入到了队尾,将队尾节点的前一个节点返回
    3. 如果当前队列不为空,通过java.util.concurrent.locks.AbstractQueuedLongSynchronizer#enq自旋设置队尾元素
  4. java.util.concurrent.locks.AbstractQueuedLongSynchronizer#enq

     private Node enq(final Node node) {
            for (;;) {
                Node t = tail;
                if (t == null) { // Must initialize
                    if (compareAndSetHead(new Node()))
                        tail = head;
                } else {
                    node.prev = t;
                    if (compareAndSetTail(t, node)) {
                        t.next = node;
                        return t;
                    }
                }
            }
        }

     

    1. 当队列为空的时候初始化队列,然后通过cas操作设置当前节点为队尾节点,如果失败了会一直尝试
    2. 首先是一个永真的循环来实现自旋操作
    3. 拿到队尾节点,如果队尾节点为空,则当前队列为空,创建一个哨兵节点,并设置为队头节点,然后将队尾节点的引用和队头节点的引用同时执行哨兵节点,也就是完成了队列的初始化
    4. 将当前节点的前驱节点设置为队尾节点,然后通过cas操作设置当前节点为队尾节点,如果设置成功,将队尾节点的前一个节点返回
  5.  

    接下来是java.util.concurrent.locks.AbstractQueuedLongSynchronizer#acquireQueued

        final boolean acquireQueued(final Node node, long arg) {
            boolean failed = true;
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; // help GC
                        failed = false;
                        return interrupted;
                    }
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } finally {
                if (failed)
                    cancelAcquire(node);
            }
        }

     

    1. ​​​​​addWaiter方法将节点加入到了队列中,这个方法就是对队列中的某个节点进行处理

    2. 也是在一个永真的循环里进行尝试设置的

    3. 这个方法在节点放入队列操作之后和队列中的节点线程被唤醒之后会被执行到

    4. 拿到当前节点的前驱节点,如果前驱节点是对头节点并且当前节点可以获取到锁(对共享变量操作成功)

      1. 为什么要前驱节点是队头节点呢? 可以类比排队,对于队伍中的每个人来说,只有的处于队伍中的第二个人才是当前“有可能”获得操作机会的,因为第一个人已经在操作了,后边的人要等第二个人操作完之后才可以操作

    5. 如果当前节点的前驱节点是队头节点并且当前节点cas变量操作成功的话,说明当前线程抢到了锁,可以继续执行操作

    6. 将当前节点设置为接头节点,因为同一时间只有一个线程可以执行到setHead方法,所以无需进行同步

          private void setHead(Node node) {
              head = node;
              node.thread = null;
              node.prev = null;
          }

       

    7. 将原来的队头节点移除

    8. 如果前驱节点不是队头节点或者是队头节点但是队头节点还没有释放锁,这个时候tryAcquire就会失败,那么就会到java.util.concurrent.locks.AbstractQueuedLongSynchronizer#shouldParkAfterFailedAcquire

  6. java.util.concurrent.locks.AbstractQueuedLongSynchronizer#shouldParkAfterFailedAcquire

       private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            int ws = pred.waitStatus;
            if (ws == Node.SIGNAL)
                /*
                 * This node has already set status asking a release
                 * to signal it, so it can safely park.
                 */
                return true;
            if (ws > 0) {
                /*
                 * Predecessor was cancelled. Skip over predecessors and
                 * indicate retry.
                 */
                do {
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
                /*
                 * waitStatus must be 0 or PROPAGATE.  Indicate that we
                 * need a signal, but don't park yet.  Caller will need to
                 * retry to make sure it cannot acquire before parking.
                 */
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            }
            return false;
        }

     

    1. 到达这里,说明当前节点没有不是第二个节点,或者抢不到锁

    2. 这里给进来的是前驱节点和当前节点

    3. 先明白一个地方,当前节点是需要被唤醒的,那么他的前驱节点的状态一定要是SIGNAL

    4. 先拿到前驱节点的状态,如果是SIGNAL,直接返回

    5. 如果状态大于零,那么说明前驱节点的状态是取消了的,详见java.util.concurrent.locks.AbstractQueuedLongSynchronizer.Node成员变量,就循环找到第一个状态是SIGNAL的节点设置为自己的钱去节点

    6. 如果前驱节点既不是SIGNAL,又不大于零,那他就是CONDITION或者是PROPAGATE,将其改为SIGNAL

  7.  

    当前驱节点的状态确保是SIGNAL之后,当前线程就可以安心阻塞了java.util.concurrent.locks.AbstractQueuedLongSynchronizer#parkAndCheckInterrupt

     
        private final boolean parkAndCheckInterrupt() {
            LockSupport.park(this);
            return Thread.interrupted();
        }

     

     

     

     

     

     

     

     

     

 

标签:node,Node,AQS,独占,pred,util,获取,前驱,节点
来源: https://blog.csdn.net/weixin_39452731/article/details/115385330

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有