AQS源码详细解读

引言

AQS是用来构建锁和其他同步组件的基础框架,它也是Java三大并发工具类(CountDownLatch、CyclicBarrier、Semaphore)的基础。ReentrantLock,甚至BlockingQueue也是基于它的实现,可以说是非常重要了。

简单介绍一下,AQS其实就是一个类,全称是AbstractQueuedSynchronizer,队列同步器。本文的重点是研究它的源码,其他的基础就不多说啦。想了解AQS基础的同学可以看一下3y大佬的文章。
后续有机会我会分享自己对ReentrantLock,LinkedBlockingQueue,线程池源码的理解~

文章导读:

  • AQS重要成员变量
  • 内部类-Node(等待队列的实现)
  • 获取资源(acquire)源码
  • 释放资源(release)源码
  • 内部类-ConditionObject(条件队列的实现)
  • 总结

一、AQS的重要成员变量

AQS中主要维护了state(锁状态的表示)和一个可阻塞的等待队列。
state是临界资源,也是锁的描述。表示有多少线程获取了锁。

private volatile int state;

关于state的get,set方法就不贴了,重要的是有个通过CAS修改state的方法。

    //设置期望值,想修改的值。通过CAS操作实现。
    protected final boolean compareAndSetState(int expect, int update) {
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

除此之外,还维护了等待队列(也叫CHL队列,同步队列)的头节点和尾节点。

    private transient volatile Node head;

    private transient volatile Node tail;

CHL队列由链表实现,以自旋的方式获取资源,是可阻塞的先进先出的双向队列。通过自旋和CAS操作保证节点插入和移除的原子性。当有线程获取锁失败,就被添加到队列末尾。下面我们来看看每个节点是怎么实现的。

二、内部类-Node

AQS的工作模式分为独占模式和共享模式,记录在节点的信息中。它还使用了模板方法设计模式,定义一个操作中算法的骨架,而将一些步骤的实现延迟到子类中。比如获取资源的方法就能很好的品味模板模式。一般地,它的实现类只实现一种模式,ReentrantLock就实现了独占模式;但也有例外,ReentrantReadAndWriteLock实现了独占模式和共享模式。下面来看Node相关源码。

        //当前节点处于共享模式的标记
        static final Node SHARED = new Node();
       
        //当前节点处于独占模式的标记
        static final Node EXCLUSIVE = null;

        //线程被取消了
        static final int CANCELLED =  1;
        //释放资源后需唤醒后继节点
        static final int SIGNAL    = -1;
        //等待condition唤醒
        static final int CONDITION = -2;
        //工作于共享锁状态,需要向后传播,
        //比如根据资源是否剩余,唤醒后继节点
        static final int PROPAGATE = -3;

        //等待状态,有1,0,-1,-2,-3五个值。分别对应上面的值
        volatile int waitStatus;

        //前驱节点
        volatile Node prev;

        //后继节点
        volatile Node next;

        //等待锁的线程
        volatile Thread thread;

        //等待条件的下一个节点,ConditonObject中用到
        Node nextWaiter;

对于等待状态(waitStatus)做一个解释。

  • CANCELLED =1 线程被取消了
  • SIGNAL =-1 释放资源后需唤醒后继节点
  • CONDITION = -2 等待condition唤醒
  • PROPAGATE = -3 (共享锁)状态需要向后传播
  • 0 初始状态,正常状态

CANCELLED

作废状态,该节点的线程由于超时,中断等原因而处于作废状态。是不可逆的,一旦处于这个状态,说明应该将该节点移除了。

SIGNAL

待唤醒后继状态,当前节点的线程处于此状态,后继节点会被挂起,当前节点释放锁或取消之后必须唤醒它的后继节点。

CONDITION

等待状态,表明节点对应的线程因为不满足一个条件(Condition)而被阻塞。

三、获取资源(锁)

获取释放资源其实都是对state变量的修改,有的文章会管他叫锁,笔者更喜欢叫资源。
获取资源的方法有acquire(),acquiredShared()。先来看acquire(),该方法只工作于独占模式

3.1 acquire()--独占模式获取资源
aquire():以独占模式获取资源,忽略中断(ReentrantLock.lock()中调用了这个方法)

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            //让线程处于一种自旋状态,
            //尝试让该线程重新获取锁!当条件满足获取到了锁则可以从自旋过程中
            //退出,否则继续。
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

之前也提到了AQS使用了模板方法模式,其实tryAcuire()方法就是一个钩子方法。在AQS中,此方法会抛出UnsupportedOperationException,所以需要子类去实现。tryAcquire(arg)返回false,其实就是获取锁失败的情况。这时候就需要做自旋,重新获取。

addWaiter():将当前线程插入至队尾,返回在等待队列中的节点(就是处理了它的前驱后继)。

    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插入末尾
        if (pred != null) {
            node.prev = pred;
            //此时可能有其他线程插入,所以使用CAS重新判断tail
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        //如果tail为空,说明队列还没有初始化,执行enq()
        enq(node);
        return node;
    }

enq():将节点插入队尾,失败则自旋,直到成功。

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //虽然tail==null才会执行本方法
            //但是可能刚好有其他线程插入,会导致
            //之前的判断失效,所以重新判断tail是否为空
            //队尾为空,说明队列中没有节点
            //初始化头尾节点
            if (t == null) { 
                if (compareAndSetHead(new Node()))
                    //初始化完成后,接着走下一个循环,
                    //直到node正常插入尾部
                    tail = head;
            } else {
                //下面就是链表的正常插入操作了
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

acquireQueued():自旋方式获取资源并判断是否需要被挂起。

final boolean acquireQueued(final Node node, int 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;
                }
                //shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()
                //parkAndCheckInterrupt()用于中断当前节点中的线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire():判断当前节点是否应该被挂起。下面涉及到的等待状态,这里再回忆一下,CANCELLED =1,SIGNAL =-1,CONDITION = -2,PROPAGATE = -3,0

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            //前驱节点的状态是SIGNAL,说明前驱节点释放资源后会通知自己
            //此时当前节点可以安全的park(),因此返回true
            return true;
        if (ws > 0) {
            //前驱节点的状态是CANCLLED,说明前置节点已经放弃获取资源了
            //此时一直往前找,直到找到最近的一个处于正常等待状态的节点
            //并排在它后面,返回false
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            //前驱节点的状态是0或PROPGATE,则利用CAS将前置节点的状态置
            //为SIGNAL,让它释放资源后通知自己
            //如果前置节点刚释放资源,状态就不是SIGNAL了,这时就会失败
            // 返回false
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

parkAndCheckInterrupt():若确定有必要park,才会执行此方法。

    private final boolean parkAndCheckInterrupt() {
        //使用LockSupport,挂起当前线程
        LockSupport.park(this);
        return Thread.interrupted();
    }

selfInterrupt():对当前线程产生一个中断请求。能走到这个方法,说明acquireQueued()返回false,确实需要被中断。

    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

到这里,获取资源的流程就走完了,接下来总结一下。
aquire的步骤:
1)tryAcquire()尝试获取资源。

2)如果获取失败,则通过addWaiter(Node.EXCLUSIVE), arg)方法把当前线程添加到等待队列队尾,并标记为独占模式。

3)插入等待队列后,并没有放弃获取资源,acquireQueued()自旋尝试获取资源。根据前置节点状态状态判断是否应该继续获取资源。如果前驱是头结点,继续尝试获取资源;

4)在每一次自旋获取资源过程中,失败后调用shouldParkAfterFailedAcquire(Node, Node)检测当前节点是否应该park()。若返回true,则调用parkAndCheckInterrupt()中断当前节点中的线程。若返回false,则接着自旋获取资源。当acquireQueued(Node,int)返回true,则将当前线程中断;false则说明拿到资源了。

5)在进行是否需要挂起的判断中,如果前置节点是SIGNAL状态,就挂起,返回true。如果前置节点状态为CANCELLED,就一直往前找,直到找到最近的一个处于正常等待状态的节点,并排在它后面,返回false,acquireQueed()接着自旋尝试,回到3)。

6)前置节点处于其他状态,利用CAS将前置节点状态置为SIGNAL。当前置节点刚释放资源,状态就不是SIGNAL了,导致失败,返回false。但凡返回false,就导致acquireQueed()接着自旋尝试。

7)最终当tryAcquire(int)返回false,acquireQueued(Node,int)返回true,调用selfInterrupt(),中断当前线程。

3.2 acquireShared()--共享模式获取资源
接下来简单说下共享模式下获取资源的流程。
acquireShared():以共享模式获取对象,忽略中断。先是tryAcquireShared(int)尝试直接去获取资源,如果成功,acquireShared(int)就结束了;否则,调用doAcquireShared(Node)将线程加入等待队列,直到获取到资源为止。

    public final void acquireShared(int arg) {
        //模板方法模式,tryAcquireShared由子类实现
        //想看的话推荐读写锁的源码,这里就不细述了
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

doAcquireShared():实现上和acquire()方法差不多,就是多判断了是否还有剩余资源,唤醒后继节点。

    private void doAcquireSharedInterruptibly(int arg)
        throws InterruptedException {
        //将线程加入等待队列,设置为共享模式
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            //自旋尝试获取资源
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        //设置头节点,且如果还有剩余资源,唤醒后继节点获取资源
                        setHeadAndPropagate(node, r);
                        p.next = null; 
                        failed = false;
                        return;
                    }
                }
                //是否需要被挂起
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

3.3 hasQueuedPredecessors()--公平锁在tryAqcuire()时调用
这里补充一个方法,ReentrantLock如果是公平锁的话,会调用AQS中的这个方法,算是后续文章的铺垫吧。
boolean hasQueuedPredecessors():判断当前线程是否位于CLH同步队列中的第一个。如果是则返回flase,否则返回true。

    public final boolean hasQueuedPredecessors() {
        //判断当前节点在等待队列中是否有前驱节点的判断,
        //如果有前驱节点说明有线程比当前线程更早的请求资源,
        //根据公平性,当前线程请求资源失败。
        //如果当前节点没有前驱节点的话,才有做后面的逻辑判断的必要性
        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());
    }

3.4 doAcquireNanos()--独占模式下在规定时间内获取锁
这个方法在ReentrantLock.tryLock()过程中被调用。
doAcquireNanos():这个方法只工作于独占模式,自旋获取资源超时后则返回false;如果有必要挂起且未超时则挂起。

    private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        //计算截至时间
        final long deadline = System.nanoTime() + nanosTimeout;
        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; 
                    failed = false;
                    return true;
                }
                //重新计算超时时间
                nanosTimeout = deadline - System.nanoTime();
                //超时则返回false
                if (nanosTimeout <= 0L)
                    return false;
                //否则判断是否需要被阻塞,阻塞规定时间
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

3.5 doAcquireInterruptibly--获取锁时响应中断
这个方法在ReentrantLock.lockInterruptibly()过程中被调用。
doAcquireInterruptibly():独占模式下在获取锁时会阻塞,但是能响应中断请求。

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        //添加到等待队列,包装成Node
        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;
                }
                //自旋,直到前驱节点等待状态为SIGNAL,检查中断标志
                //符合条件则阻塞当前线程
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    //当前线程被阻塞后,会中断
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

四、释放资源(锁)

同样的,释放资源也分为释放独占锁资源(release())和共享锁(releaseShared())资源。
先来看对于独占锁的释放。

4.1 release()--独占模式释放资源
release():工作于独占模式,首先调用子类的tryRelease()方法释放锁,然后唤醒后继节点,在唤醒的过程中,需要判断后继节点是否满足情况,如果后继节点不为空且不是作废状态,则唤醒这个后继节点,否则从tail节点向前寻找合适的节点,如果找到,则唤醒。

    public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

unparkSuccessor():尝试找到下一位继承人,就是确定下一个获取资源的线程,唤醒指定节点的后继节点。

    private void unparkSuccessor(Node node) {
        //如果状态为负说明是除CANCEL以外的状态,
        //尝试在等待信号时清除。
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        //下一个节点为空或CANCELLED状态
        //则从队尾往前找,找到正常状态的节点作为之后的继承人
        //也就是下一个能拿到资源的节点
        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.2 releaseShared()--共享模式释放资源
releaseShared():在释放一部分资源后就可以通知其他线程获取资源。

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }

五、内部类-ConditionObject

ConditionObject实现了Condition接口。用于线程间的通信,能够把锁粒度减小。重点是await()和signal()。这个内部类还维护了一个condition队列,而且Node.nextWaiter就是用来将condition连接起来的。

        //condition队头
        private transient Node firstWaiter;
        //condition队尾
        private transient Node lastWaiter;
        //发生了中断,但在后续不抛出中断异常,而是“补上”这次中断
        private static final int REINTERRUPT =  1;
        //发生了中断,且在后续需要抛出中断异常
        private static final int THROW_IE    = -1;

5.1 await()--阻塞等待方法

5.1.1 await的流程

await():当前线程处于阻塞状态,直到调用signal()或中断才能被唤醒。

1)将当前线程封装成node且等待状态为CONDITION。
2)释放当前线程持有的所有资源,让下一个线程能获取资源。
3)加入到条件队列后,则阻塞当前线程,等待被唤醒。
4)如果是因signal被唤醒,则节点会从条件队列转移到等待队列;如果是因中断被唤醒,则记录中断状态。两种情况都会跳出循环。
4)若是因signal被唤醒,就自旋获取资源;否则处理中断异常。

        public final void await() throws InterruptedException {
            //如果被中断,就处理中断异常
            if (Thread.interrupted())
                throw new InterruptedException();
            //初始化链表的功能,设置当前线程为链尾
            Node node = addConditionWaiter();
            //释放当前节点持有的所有资源
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //如果当前线程不在等待队列中,
            //说明此时一定在条件队列里,将其阻塞。
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                //说明中断状态发生变化
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //当前线程执行了signal方法会经过这个,也就是重新将当前线程加入同步队列中
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            //删除条件队列中不满足要求的元素
            if (node.nextWaiter != null) 
                unlinkCancelledWaiters();
            //处理被中断的情况
            if (interruptMode != 0)
                //这里是个难点,具体的实现我自己也有点不理解
                //就把知道的都写出来
                //如果是THROW_IE,说明signal之前发生中断
                //如果是REINTERRUPT,signal之后中断,
                //所以成功获取资源后会调用selfInterrupt()
                reportInterruptAfterWait(interruptMode);
        }

addConditionWaiter():将当前线程封装成节点,添加到条件队列尾部,并返回当前节点。

        private Node addConditionWaiter() {
            Node t = lastWaiter;
            // 判断队尾元素,如果非条件等待状态则清理出去
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                //可能t之前引用的节点被删除了,所以要重新引用
                t = lastWaiter;
            }
            //这个节点就表示当前线程
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            //说明条件按队列中没有元素
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

unlinkCancelledWaiters():遍历一遍条件队列,删除非CONDITION状态的节点。

        private void unlinkCancelledWaiters() {
            Node t = firstWaiter;
            //记录在循环中上一个waitStatus有效的节点
            Node trail = null;
            while (t != null) {
                Node next = t.nextWaiter;
                //再次判断等待状态,保证节点都是CONDITION状态
                //确保当前节点无效后删除引用
                if (t.waitStatus != Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        //否则就直接加到队尾的后面
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    //记录有效的节点并向后遍历
                    trail = t;
                t = next;
            }
        }

5.1.2 await中关于中断的处理

通过对上面代码的观察,我们知道await()调用了checkInterruptWhileWaiting()。
关于中断这一块,我自己看的也比较迷,就把一些自己能理解的地方标注一下。
checkInterruptWhileWaiting():判断在阻塞过程中是否被中断。如果返回THROW_IE,则表示线程在调用signal()之前中断的;如果返回REINTERRUPT,则表明线程在调用signal()之后中断;如果返回0则表示没有被中断。

        private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }

transferAfterCancelledWait():线程是否因为中断从park中唤醒。

    final boolean transferAfterCancelledWait(Node node) {
        //如果修改成功,暂且认为中断发生后,signal()被调用
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            //true表示中断先于signal发生
            return true;
        }
        //~~不理解~~
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

5.2.1 signal()--唤醒condition队列中的线程

signal():唤醒一个被阻塞的线程。

        public final void signal() {
            //判断当前线程是否为资源的持有者
            //这也是必须在lock()与unlock()代码中间执行的原因
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //开始唤醒条件队列的第一个节点
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

doSignal():将条件队列的头节点从条件队列转移到等待队列,并且,将该节点从条件队列删除。

        private void doSignal(Node first) {
            do {
                //后续的等待条件为空,说明condition队列中只有一个节点
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
                //transferForSignal()是真正唤醒头节点的地方
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

transferForSignal():将节点放入等待队列并唤醒。并不需要在条件队列中移除,因为条件队列每次插入时都会把状态不为CONDITION的节点清理出去。

    final boolean transferForSignal(Node node) {
        //当前节点等待状态改变失败,则说明当前节点不是CONDITION
        //状态,那就不能进行接下来的操作,返回false
        //0是正常状态
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //放入等待队列队尾中,并返回之前队列的前一个节点
        Node p = enq(node);
        int ws = p.waitStatus;
        //如果节点没有被取消,或更改状态失败,则唤醒被阻塞的线程
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

5.2.2 signalAll()--唤醒condition队列中所有线程
signalAll()本质上还是调用了doSignalAll()
doSignalAll():遍历条件队列,插入到等待队列中。

        private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

5.3 awaitNanos()--超时机制
这里补充一个方法awaitNanos(),是我看阻塞队列源码中遇到的。
awaitNanos():轮询检查线程是否在同步线程上,如果在则退出自旋。否则检查是否已超过解除挂起时间,如果超过,则退出挂起,否则继续挂起线程到等待解除挂起。退出挂起之后,采用自旋的方式竞争锁。

        public final long awaitNanos(long nanosTimeout)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            final long deadline = System.nanoTime() + nanosTimeout;
            int interruptMode = 0;
            //采用自旋的方式检查是否已在等待队列当中
            while (!isOnSyncQueue(node)) {
                //如果挂起超过一定的时间,则退出
                if (nanosTimeout <= 0L) {
                    transferAfterCancelledWait(node);
                    break;
                }
                //继续挂起线程
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
                nanosTimeout = deadline - System.nanoTime();
            }
            //采用自旋的方式竞争锁
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
            return deadline - System.nanoTime();
        }

总结

关于AQS我觉得比较重要的就是获取资源和释放资源的方法,里面用到了大量的CAS操作和自旋。AQS里面维护了两个队列,一个是等待队列(CHL),还有一个是条件队列(condition)。
acquire()尝试获取资源,如果获取失败,将线程插入等待队列。插入后,并没有放弃获取资源,而是根据前置节点状态状态判断是否应该继续获取资源。如果前置节点是头结点,继续尝试获取资源;如果前置节点是SIGNAL状态,就中断当前线程,否则继续尝试获取资源。直到当前线程被阻塞或者获取到资源,结束。
release()释放资源,需要唤醒后继节点,判断后继节点是否满足情况。如果后继节点不为空且不是作废状态,则唤醒这个后继节点;否则从尾部往前面找适合的节点,找到则唤醒。
调用await(),线程会进入条件队列,等待被唤醒,唤醒后以自旋方式获取资源或处理中断异常;调用signal(),线程会插入到等待队列,唤醒被阻塞的线程。

如有不当之处,欢迎评论指出~
如果喜欢我的文章,欢迎关注知乎专栏Java修仙道路~

参考文章:Java并发编程札记,AQS简简单单过一遍,Condition源码分析,JUC.Condition学习笔记[附详细源码解析]

你可能感兴趣的:(并发)