AQS(AbstractQueuedSynchronizer)源码解析

一、前言

本篇是看了下面大神的专栏用来总结记录,因为自己写一遍总比看一遍记得清楚,但强烈建议想要详细了解AQS还是去阅读下面大神的专栏,而非我这篇文章。https://segmentfault.com/a/1190000015739343

二、介绍

1. AQS 简介

AQS是AbstractQueuedSynchronizer的简称。AQS提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架,如下图所示。AQS为一系列同步器依赖于一个单独的原子变量(state)的同步器提供了一个非常有用的基础。子类们必须定义改变state变量的protected方法,这些方法定义了state是如何被获取或释放的。鉴于此,本类中的其他方法执行所有的排队和阻塞机制。子类也可以维护其他的state变量,但是为了保证同步,必须原子地操作这些变量。
在这里插入图片描述
AQS提供独占和共享两种模式,独占顾名思义,同一时间只能有一个线程占有锁,共享则是同一时间可以有多个线程拥有锁。

2. Node 数组

AbstractQueuedSynchronizer 中,队列的实现是一个双向链表,他的每个节点是一个Node类型
Node是 AbstractQueuedSynchronizer 的一个内部类,下面省略了部分代码:

  static final class Node {
  		// 共享锁和独占锁的判断标志
        static final Node SHARED = new Node();
        static final Node EXCLUSIVE = null;
		
		// waitStatus 可选值
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;
		// 节点的等待状态,这个后面详细介绍
        volatile int waitStatus;
		// 当前节点的前置节点
        volatile Node prev;
		// 当前节点的后置节点
        volatile Node next;
       // Node 数组中所代表的线程
        volatile Thread thread;

        // 标志位,如果是null则说明是独占锁,不为null说明是共享锁
        Node nextWaiter;

        // 判断是否是共享锁
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        // 返回前置节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

      	....
    }

(1) waitStatus : 表示节点所处的等待状态

上面只有四种状态,实际上是有五种,

解释
CANCELLED值为1,代表当前Node取消排队等待
SIGNAL值为-1,这个状态代表的并不是本身,而是代表当当前节点取消或者获取到锁时,需要唤醒后继的节点
CONDITION值为-2,在条件队列中才有用,代表线程处于正常的等待状态
PROPAGATE值为-3, 共享锁中使用,应该被无条件的传播到其他节点
0节点初始化时waitStatus 的初始值

(2) prev、next 前后节点

因为AQS中的队列的实现是一个双向链表,所以需要连接前后节点,prev指向前置节点,next指向后置节点。

(3) thread

Node节点保存的线程信息

(4) nextWaiter

标志当前模式是共享还是独占。如果为null,说明是独占模式,如果为一个Node节点(这个节点不代表任何线程信息,仅仅用来标识),则说明是共享模式。

三、独占锁的实现

从ReentrantLock类中看
ReentrantLock#lock -> ReentrantLock.FairSync#lock -> AbstractQueuedSynchronizer#acquire

1. 独占锁的获取

(1). 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) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

从上面的代码可以看到,如果tryAcquire(arg) 尝试获取锁失败后才会执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 的代码。我们先来看tryAcquire(arg)方法。 tryAcquire方法在 AbstractQueuedSynchronizer 并没有具体的实现,因为尝试获取锁的逻辑一般是根据子类的需求来的,所以这个方法在子类中有具体的实现,如下:

(2) ReentrantLock.FairSync#tryAcquire

        /**
         * 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();
            // 获取标记量,如果等于0则说明还没有线程获取锁
            int c = getState();
            if (c == 0) {
            	// 如果等待队列中没有在当前线程前面的等待线程,则使用CAS将state置为acquires,
            	//	并且记录下来获取锁的线程(因为是独占锁)
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            // 如果state不等于0,则说明有线程获得了锁,判断是否是当前线程获取的锁,如果是,则累加state(因为是可重入锁)
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

(3).AbstractQueuedSynchronizer#addWaiter

顺着上面的逻辑走下来。 如果 tryAcquire 尝试获取锁失败后,会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 方法。
在这里插入图片描述
addWaiter()方法是将当前获取锁失败的线程放入等待队列中。Node.EXCLUSIVE 代表的是独占模式,其实就是null。而共享模式则是一个没有其他意义的Node节点。
在这里插入图片描述
而在Node的构造函数中可以看到 Node 节点 使用 nextWaiter 作为一个标识,为null则代表是独占模式,一个Node节点则代表共享模式,但是这个Node节点不代表任何线程,仅仅做一个标识意义。
在这里插入图片描述
下面具体分析 addWaiter 方法

/**
    /**
     * Creates and enqueues node for current thread and given mode.
     *
     * @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
     * @return the new node
     */
    private Node addWaiter(Node mode) {
    	// 将当前线程包装成一个Node节点
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        //  如果队列不为空(因为tail是指向尾结点,如果他为空,则说明队列为空), 则将当前线程包装成Node节点入队末尾。
        // 并且将tail 指向队尾节点。
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        // 如果队列为空,或者队尾元素已经变化(compareAndSetTail(pred, node) cas 操作失败),则会调用enq
        enq(node);
        return node;
    }
    
     * 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;
            // 如果队列为空,则创建一个头结点,这个头结点是新new出来的,所以不包含任何数据。
            // 外层是个循环,跳出循环的唯一办法就是走else支路
            if (t == null) { // Must initialize
            	// 创建头节点
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	// 走到这里说明队列已经不为空,至少有了头结点。
            	// 让node前置节点指向 tail所指向的节点, 之后并设置tail指向node节点.(这里会造成尾分叉)
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

addWaiter 的 完整的逻辑(我们假设现在Node队列是空,来走一遍逻辑):
1. 线程a进入addWaiter方法,将线程a封装成一个Node节点 an。
2. 判断pred是否为null,这里因为Node队列为空,所以pred 必定为null,所以走向end(node);
3. 进入enq后,第一次循环,tail必然是null,则t为null,进入if 里面调用 compareAndSetHead(new Node()) 初始化了一个空的头结点(不包含任何线程信息的头结点)并且让 tail指向 head。随后进入第二次循环
4. 第二次循环t不为null,所以走else。让 an.prev 指向 t,即指向了head。然后CAS设置尾指针指向an,并返回节点。
5. 此时线程b进入addWaiter方法,将线程a封装成一个Node节点 bn。这时pred不为空,所以走进if里面。if中做的事情就是将bn入到队列尾,并使tail指向bn。

流程图如下:
在这里插入图片描述
注意:
在这里插入图片描述
因为 node.prev = t; 和后面的 if 操作并不是原子操作,所以导致在并发情况,an执行完 node.prev = t 后,bn进来也执行了 node.prev = t 。这样就导致两个新节点的前置节点都是尾结点。如下图,所以在AQS中很多循环都是倒序循环,因为存在尾分叉的情况,一个节点要能入队,则它的prev属性一定是有值的,但是它的next属性可能暂时还没有值。
在这里插入图片描述

(4). AbstractQueuedSynchronizer#acquireQueued

addWaiter返回当前线程封装成的一个Node节点, 并将该节点加入了等待队列中。
接下来看 acquireQueued 方法

    /**
     * Acquires in exclusive uninterruptible mode for thread already in
     * queue. Used by condition wait methods as well as acquire.
     *
     * @param node the node
     * @param arg the acquire argument
     * @return {@code true} if interrupted while waiting
     */
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	// 获取node节点的前置节点
                final Node p = node.predecessor();
                // 如果前置节点是头结点,则说明当前节点已经是等待线程中最前面的了(因为头结点并不代表任何等待线程),调用tryAcquire()尝试获取锁。
                if (p == head && tryAcquire(arg)) {
                	// 如果锁获取成功,则将node设置为头节点(清空了锁代表的线程信息,可以理解为变相的出队),并返回
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                // 如果node前面还有等待的节点,则判断是否需要将当前线程挂起。
                // 设置好闹钟后(shouldParkAfterFailedAcquire 返回true), 调用parkAndCheckInterrupt() 挂起线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    ....
    // 可以看到如果node获取到锁,那么它将成为头结点,但是他的信息也被清空,不代表任何线程信息。
   	private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }
	...
	    /**
     * Checks and updates status for a node that failed to acquire.
     * Returns true if thread should block. This is the main signal
     * control in all acquire loops.  Requires that pred == node.prev.
     *
     * @param pred node's predecessor holding status
     * @param node the node
     * @return {@code true} if thread should block
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        // 如果前置节点的状态为 SIGNAL, 则说明已经设置了唤醒状态(订好了闹钟),直接返回true。
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        // 如果 前置节点的 ws 大于0(其实也就是取消状态),则说明前置节点已经取消排队了,则跳过这些取消的节点,直接跳到未取消的节点
        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.
             */
             // 否则的话,将前置节点状态置为SIGNAL,即后面的节点需要前置节点唤醒
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

(5). 总结

假设两个线程 t1、t2 需要获取锁。

  1. 当t1获取锁时,由于之前没有线程获取锁,所以 tryAcquire(arg) 尝试获取锁成功,返回true。注意 !tryAcquire(arg) 为false,直接返回,此时并没有创建Node队列,连头结点也没有创建。
    在这里插入图片描述
  2. t1还未释放锁时,t2获取锁,这时候 tryAcquire(arg) 获取失败,返回false,所以会调用 addWaiter 方法创建一个头结点的队列,并且将当前线程添加进去。
    在这里插入图片描述
  3. 顺着上面,进去enq方法。第一遍循环如下。
    在这里插入图片描述
    创建一个如下的头结点,其中nextWaiter懒得画, 反正也不影响这里
    在这里插入图片描述
    注意外围是一个for循环,跳出循环的唯一办法就是走进else,所以我们第二次循环走进else,else会创建一个t2的node节点,并追加到head后,并肩t2的节点返回。
    在这里插入图片描述
  4. t2 继续进入 acquireQueued 方法。这时会 p == head 返回true,但是 tryAcquire(arg) 肯定失败,因为这是是独占锁,t1还未释放锁,t2必然获取不到。

在这里插入图片描述
6. 随后执行 shouldParkAfterFailedAcquire 方法,在这个方法中会改变head节点的waitStatus状态(仅仅是当前这个例子的情况下,并非每一次改变的都是头结点)
在这里插入图片描述
如下:
在这里插入图片描述
7. 至此,线程t2加入到了Node等待队列。随后调用 parkAndCheckInterrupt 阻塞线程

2. 独占锁的释放

我们 也从 RentrantLock#unlock 开始分析

(1). RentrantLock#unlock ->AbstractQueuedSynchronizer#release

因为RentrantLock 是独占锁,所以在下面的方法中当 tryRelease 方法返回true(即锁释放成功后),才会唤醒下一个等待线程。

    /**
     * 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 的实现也是在子类中
        if (tryRelease(arg)) {
            Node h = head;
            // 释放锁成功后,开始唤醒后继节点的线程
            // h != null 说明队列不为空,h.waitStatus !=0 说明需要唤醒后记节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

这里来详细说明一下 if (h != null && h.waitStatus != 0) 的条件,h != null 必然是队列不为空,队列都为空了还有什么好唤醒的。
问题是第二个条件 h.waitStatus != 0
更新waitStatus的地方只有AbstractQueuedSynchronizer#shouldParkAfterFailedAcquirecompareAndSetWaitStatus(pred, ws, Node.SIGNAL); 将ws更新成 Node.SIGNAL
而由于h是head节点,即头结点,那么什么时候会将头结点的waitStatus 状态更新呢?可以看到下面的图
在这里插入图片描述
不执行 shouldParkAfterFailedAcquire 方法的条件是 p == head && tryAcquire(arg)。当p为头结点时,如果tryAcquire(arg) 尝试获取锁失败,仍然会执行 shouldParkAfterFailedAcquire 方法,并且会将 head头结点的waitStatus置为 Node.SIGNAL换句话说,如果head的waitStatus == 0 时,则说明头结点后面没有正在等待的节点。

(2). ReentrantLock.Sync#tryRelease

        protected final boolean tryRelease(int releases) {
            int c = getState() - releases;
            // 如果当前线程不是拥有锁的线程,则抛出异常
            if (Thread.currentThread() != getExclusiveOwnerThread())
                throw new IllegalMonitorStateException();
            boolean free = false;
            // 如果更新后的状态等于0,则算释放了锁(因为可能是可重入锁,所以一次释放不一定完全释放)
            if (c == 0) {
                free = true;
                // 设置拥有锁的线程为null,即没有线程拥有锁
                setExclusiveOwnerThread(null);
            }
            // 更新状态值为0
            setState(c);
            return free;
        }

(3). AbstractQueuedSynchronizer#unparkSuccessor

如果一个线程被挂起了, 它的前驱节点的 waitStatus值必然是Node.SIGNAL

  /**
     * Wakes up node's successor, if one exists.
     *
     * @param node the node
     */
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
         // 如果head 的 ws 小于0,将其状态更新成0。
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
         // 一般情况下,我们只需要唤醒即是自己的后继节点
         // 但是无法保证后继节点是否已经取消了排队
         //所以这里是为了找到离自己最近的后继有效节点
        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);
    }

(4)总结

  1. t1执行结束了,释放锁了。这时候队列的状态如下图。
    在这里插入图片描述
  2. t1 调用 release 方法释放锁。
    在这里插入图片描述
  3. 因为锁是t1占用,所以释放成功。我们继续看下面的条件,介绍如下:
    在这里插入图片描述
  4. 我们满足上述条件,进入 unparkSuccessor 方法。在这里插入图片描述,5. 这时候的队列状态如下,又回到了一开始的样子。
    在这里插入图片描述
    下面根据个人理解解释一下 为什么 unparkSuccessor 方法要将head的节点waitStatus置为0
    我们假设,head.watisStatus状态仍为 SIGNAL 。那么当t2释放锁时,他会再去执行
    AbstractQueuedSynchronizer#unparkSuccessor 来释放线程,虽然最终结果相同,但是造成了不必要消耗。这也是他为什么没有判断是否更状态成功,并且官方注释上写着失败也无妨,因为确实失败也无妨。。。。
    在这里插入图片描述

四、共享锁的实现

关于共享锁,共享锁的很多代码和独占锁类似,所以下面的讲解并没有那么细致。
我们通过 Semaphore 来进行研究

1. 共享锁的获取

我们从Semaphore#acquire() -> AbstractQueuedSynchronizer#acquireSharedInterruptibly来看
在这里插入图片描述

(1). AbstractQueuedSynchronizer#tryAcquireShared

tryAcquireShared在子类中实现,Semaphore中有公平锁和非公平锁两种实现,我们挑公平锁的实现看一看
Semaphore.FairSync#tryAcquireShared

  		protected int tryAcquireShared(int acquires) {
            for (;;) {
            	// 如果等待队列中还有节点在当前线程前面,则说明没轮到当前线程,返回-1
                if (hasQueuedPredecessors())
                    return -1;
                // 到达这里说明已经是等待队列中最前面的节点了
                int available = getState();
                int remaining = available - acquires;
                // 因为是共享锁,可能有多个线程同时拥有,只要剩余量remaining 大于0,说明还有通路,所以还可以允许其他线程申请,小于0或者CAS失败(失败则说明有其它线程可能更新了剩余量),则说明通路不够,返回剩余量
                if (remaining < 0 ||
                    compareAndSetState(available, remaining))
                    return remaining;
            }
        }

(2). AbstractQueuedSynchronizer#doAcquireSharedInterruptibly

doAcquireSharedInterruptibly 里面的很多代码都在独占锁讲过了,所以这里简化讲解过程。setHeadAndPropagate 后面单独讲

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        // 将当前线程封装成一个Node节点,传参是代表共享节点
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            for (;;) {
            	// 获取前置节点
                final Node p = node.predecessor();
                // 如果前置节点是头结点,说明轮到自己等待获取锁
                if (p == head) {
                	// 尝试获取锁 -》 这里返回的是个int值,这里也是和独占锁的一个不同之处
                	// 独占锁返回boolean类型,因为锁只能被一个线程占用,其它线程不能占有
                	// 共享锁则是多个线程可以占有,如果返回值r 大于0,则说明还有剩余通路,将当前节点设置为头结点,清空节点信息(变相出队)后返回。
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                    	// 将当前获取锁的节点作为头结点,并且清空其信息(我只是一具没有感情的节点尸体..)也就是说的变相出队。因为虽然这个节点还在队列中,但是他已经作为头结点使用,并且没有任何线程信息。
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        failed = false;
                        return;
                    }
                }
                // 达到这里,则说明获取锁失败, 这里和独占锁相同,不再讲解,即将线程节点挂起等待。
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

(3). AbstractQueuedSynchronizer#setHeadAndPropagate

 private void setHeadAndPropagate(Node node, int propagate) {
 		// 这里都很类似,设置头节点
        Node h = head; // Record old head for check below
        setHead(node);
      
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
          
            if (s == null || s.isShared())
            	// 传播释放锁
                doReleaseShared();
        }
    }

(4). 总结

  1. 假设等待队列如下,有头结点head-t1(标志是t1线程转换成的头结点)
    在这里插入图片描述
  2. 这时候线程t2获取锁。我们假设 t2 线程现在执行 Semaphore#acquire() -> AbstractQueuedSynchronizer#acquireSharedInterruptibly来获取一个锁。

在这里插入图片描述
3. 我们假设 t2 线程现在执行 doAcquireSharedInterruptibly来再次获取一次锁,进入到这里其实已经获取失败了(在acquireSharedInterruptibly 中已经执行过一次tryAcquireShared 并且获取失败了),所以将其加入到等待队列。
在这里插入图片描述
4. 进入 if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) 中阻塞队列。 (shouldParkAfterFailedAcquire(p, node) 返回true即代表将前置节点的waitStatus设置为SIGNAL状态了。
在这里插入图片描述
这时候的队列状态应该为下图。
在这里插入图片描述
5. 按照上面的逻辑,我们假设还有线程t3、t4入队等待,队列如下图
在这里插入图片描述

2. 共享锁的释放

(1). AbstractQueuedSynchronizer#releaseShared

Semaphore#release() -> AbstractQueuedSynchronizer#releaseShared
可以看到如果 tryReleaseShared 释放锁成功,开始解除线程的阻塞

    /**
     * Releases in shared mode.  Implemented by unblocking one or more
     * threads if {@link #tryReleaseShared} returns true.
     *
     * @param arg the release argument.  This value is conveyed to
     *        {@link #tryReleaseShared} but is otherwise uninterpreted
     *        and can represent anything you like.
     * @return the value returned from {@link #tryReleaseShared}
     */
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

Semaphore.Sync#tryReleaseShared 比较简单,不做过多解释

        protected final boolean tryReleaseShared(int releases) {
            for (;;) {
                int current = getState();
                int next = current + releases;
                if (next < current) // overflow
                    throw new Error("Maximum permit count exceeded");
                if (compareAndSetState(current, next))
                    return true;
            }
        }

(2).AbstractQueuedSynchronizer#doReleaseShared

    private void doReleaseShared() {
      
        for (;;) {
            Node h = head;
            // 代表着队列中除了头结点应该还有一个实际等待节点,即至少两个节点
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                // 1
                if (ws == Node.SIGNAL) {
                	// 这里通过CAS操作保证了unparkSuccessor(h)只被执行一次。因为CAS保证只有一个线程可以修改成功
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    // 释放线程
                    unparkSuccessor(h);
                }
                //  2. Node.PROPAGATE 标志应该无条件传播。
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            // 3
            if (h == head)                   // loop if head changed
                break;
        }
    }

解释:这一段强烈建议看上面推荐的原文章,这一段写的估计只有我自己能看懂。。。。。
1. if (ws == Node.SIGNAL) 显而易见,如果ws状态为SINGNAL ,则需要唤醒后继节点
2. else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) 。 前半段,ws == 0 说明这个节点是当前队列的最后一个节点成为了头结点,因为节点挂起时会让前置节点的waitStatus置为SINGNAL。后半段,当 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE) 返回true时,也就是compareAndSetWaitStatus(h, 0, Node.PROPAGATE) 失败了,也就是说再进行这个操作的时候,有新节点入队。AbstractQueuedSynchronizer#doAcquireInterruptibly -> AbstractQueuedSynchronizer#shouldParkAfterFailedAcquire 方法中设置了前置节点(即头结点)的waitStatus的状态为SINGNAL。这时候 continue,继续循环唤醒节点()。
3. 这里为什么要判断 h == head ? 因为 doReleaseShared 方法在 setHeadAndPropagate和releaseShared中都调用doReleaseShared 方法。这就导致当前线程执行这个方法到结尾时,其它线程可能也调用了doReleaseShared 方法并且修改了头结点,当头结点不一致时,则继续进入循环修改。

(3). 总结

  1. 假设等待队列如下,有头结点head-t1(标志是t1线程转换成的头结点)

在这里插入图片描述

  1. 我们假设这时候 t1线程执行结束。会调用 Semaphore#release(int) -> AbstractQueuedSynchronizer#releaseShared -> AbstractQueuedSynchronizer#doReleaseShared 方法释放锁。
    在这里插入图片描述

  2. 当t1进入 doReleaseShared 方法去释放后继节点。很显然,这里的head 即是head-t1。显然满足 if (h != null && h != tail) 的条件 ( if (h != null && h != tail) 标志着着队列中包含头结点的情况下至少有两个节点,也就至少有一个等待线程节点)。所以进入 if 中。并且通过compareAndSetWaitStatus(h, Node.SIGNAL, 0)head-t1waitStatus值置为0。这里的CAS操作是为了确保多个线程来操作时,只有一个线程能够修改头结点的值,也就是能执行下面的 unparkSuccessor(h); 语句释放线程, 这里释放了线程 t2。
    在这里插入图片描述

  3. 当线程 t2 释放后,t2线程继续自己的for循环, doAcquireShared 方法中的循环,尝试获取锁。假设t2获取锁成功,这时候进入 setHeadAndPropagate方法中 还会 执行 doReleaseShared 方法,并且在获取成功后还会修改头结点。
    在这里插入图片描述
    这时候队列变成如下:
    在这里插入图片描述

  4. 需要注意的是: 这时候有两个线程在执行 doReleaseShared 方法 一个是刚开始释放锁的线程t1,一个是刚抢到锁的t2。对t1来说,当他执行到 if (h == head) 时,t2可能已经执行结束了 setHeadAndPropagate 方法更换了头结点,也就是说现在的头结点可能已经变成了head-t2,所以这时候对于t1线程来说会再次进入下一次循环,进行下一次的释放锁的调用。这样就形成了一个调用风暴,加快了释放锁的过程。(这里的描述仅仅是说有这种情况,并不代表每一次释放锁都会造成这样的情况
    在这里插入图片描述

  5. 重复上述过程将 t3、t4 也释放掉后,队列变成如下
    在这里插入图片描述

  6. 这个时候我们再看 doReleaseShared 方法时, else if 分支什么时候可以进入 ws == 0 只有队尾节点成为了头结点时才会如此。我们假设一个情况, if (h != null && h != tail) 条件后(这个条件有保证了必须有两个节点)。我们上面的图明显不满足这个条件。

  7. 那么假设这时候有了线程 t5 获取锁,执行 doAcquireShared 方法,并且获取锁失败,那么会执行 shouldParkAfterFailedAcquire 方法。我们这里就卡一个时间点,t5线程将要执行 shouldParkAfterFailedAcquire 方法但是还未执行的时候,因为 shouldParkAfterFailedAcquire 方法中将前置节点的waitStatus置为 SIGNAL ,但这里还未执行,所以前置节点的waitStatus仍保持原状。
    在这里插入图片描述

  8. 这时候的队列状态就为下图,这时候就满足了 doReleaseShared 方法中的 if (h != null && h != tail) 条件和 else if (ws == 0条件。
    在这里插入图片描述

  9. 再看后半段的条件 !compareAndSetWaitStatus(h, 0, Node.PROPAGATE) 想要他成立,那么也就是说头结点head-t4waitStatus不为0了, 那么就是t5线程已经在 shouldParkAfterFailedAcquire 方法中将前置节点的waitStatus置为 SIGNAL 。这时候队列状态为。当达到这个情况时, head-t4会执行 continue; 然后再次开始循环唤醒线程。
    在这里插入图片描述

五、总结:

本篇文章内容贼长,叙述及其混乱。因为AQS还是很厉害的,看的我都一懵一懵的,所以写也懵,内容中不免有些错误,欢迎指正。另一方面,强推大佬的AQS四部曲 https://segmentfault.com/a/1190000015739343
。本文可以算是四部曲的心得体会(话说还有一部曲的心得没写,篇幅太长,下回分解)

以上:内容部分参考网络
https://www.jianshu.com/p/da9d051dcc3d
https://segmentfault.com/a/1190000015739343
https://segmentfault.com/a/1190000016447307
https://segmentfault.com/a/1190000015752512
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

猫吻鱼

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值