AQS源码(jdk1.8)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/XH413235699/article/details/89033465

AQS(AbustactQueuedSynchronizer)队列同步器是Java同步的基础组件,ReentrantLock,ReentrantReadWriteLock,CountDownLatch,CyclicBarrier,Semaphore等都是基于AQS来实现的,了解AQS的源码对于多线程编程还是有一些帮助,也可以实现自定义的同步组件。

AQS使用了模板方法设计模式,提供了独占锁/共享锁获取以及释放等大概8个模板方法,为用户空出来几个供重写来实现自定义同步逻辑的方法,此外AQS还有一个Condition内部类,提供了类似于wait/notify的等待/通知机制。整体上是通过一个FIFO非阻塞同步队列和CAS操作实现非阻塞的锁获取,释放,线程的阻塞唤醒则是通过Unsafe库的park,unpark实现。源码中大量使用了CAS操作,如果不了解可以先去了解一下。

  1. AQS的基本字段
private transient volatile Node head; 

private transient volatile Node tail;

private volatile int state;

    /**
     * 1.超时时间小于等于spinForTimeoutThreshold就会采用自旋
     * 2.超时时间大于spinForTimeoutThreshold会让线程阻塞
     */
static final long spinForTimeoutThreshold = 1000L;
  1. ASQ的关键内部类 Node类

        static final Node SHARED = new Node(); //共享模式

        static final Node EXCLUSIVE = null; //独占模式
        
        static final int CANCELLED =  1; //表示线程已经被中断或者等待超时,需要从同步队列中删除

        static final int SIGNAL    = -1; //表示后继线程处于等待状态,当前节点

        static final int CONDITION = -2; //表示当前节点等待在某个Condition上
   
        static final int PROPAGATE = -3; //用在共享模式下,表示下次同步状态会无条件传播下去

		volatile Node prev;

		volatile Node next;

		volatile Thread thread;

		Node nextWaiter; //节点是否共享/独占 和 等待对列中的下一个节点共用这一字段

		final boolean isShared() { return nextWaiter == SHARED; } //判断是否共享节点,就是根据nextWaiter

下面着重分析一下AQS的几个模板方法

public final void acquire(int arg) //获取独占锁

public final void acquireInterruptibly(int arg) //获取独占锁,可响应中断

public final boolean tryAcquireNanos(int arg, long nanosTimeout) //获取独占锁,超时返回,可响应中断

public final void acquireShared(int arg) //获取共享锁

public final void acquireSharedInterruptibly(int arg) //获取共享锁,可响应中断

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) //获取共享锁,超时返回,可响应中断
  
public final boolean release(int arg) //释放独占锁 

public final void acquireShared(int arg) //释放共享锁
  1. 获取独占锁函数
    public final void acquire(int arg) {
        /**
         * 1. 根据if短路,当获取锁成功后就不执行后续代码
         * 2. 获取锁失败,先通过addWaiter方法将创建一个独占线程节点,加入阻塞队列
         * 3. 然后让阻塞队列中的所有线程都自旋方式来获取锁
         * 4. 如果获取锁失败,并且加入队列失败,或者超时等,会进入if体,中断自己
         */
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire函数是空出来留给用户实现具体的获取逻辑,需要线程安全的获取同步状态,获取失败之后,先调用addWaiter方法

    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode); //新建一个独占/共享模式的线程节点
        //进行一次快速insert操作,如果成功就返回
        //否则就进入enq方法,死循环的CAS尝试将自己加入尾结点
        Node pred = tail;
        if (pred != null) {
            /**
             * 首先将自己连接到链表的tail上
             * 然后CAS替换tail,如果此时成功,就说明上一句没有受到并发影响,乐观机制
             * 最后把原来的tail的next指向自己,因为自己现在是tail
             */
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                /**
                 * 只要CAS操作通过,下面这条语句就不会出现并发问题
                 * 因为每个线程的node都是不同的,只要保证pred是当前的node的前驱就可以
                 * 而CAS已经经过校验保证了这一点
                 */
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
    }

    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // 如果没有tail节点,说明队列为空,随便加一个
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) { //尝试CAS设置尾结点为node
                    t.next = node;
                    return t;
                }
            }
        }
    }

addWaiter方法通过CAS将并发添加变的串行化,添加完成之后,调用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;
                }
                /**
                 * 非头结点一直检测自己的前驱状态,如果发现自己的前驱节点有变化
                 * 就立刻进行下一次获取锁的尝试,否则通过lockSupport来继续park
                 * park(阻塞)是通过unsafe包来实现的,native方法
                 */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true; //在park中出现中断,会执行到此,但是不会响应中断
            }
        } finally {
            if (failed) //线程中断,需要取消获取锁
                cancelAcquire(node);
        }
    }

shouldParkAfterFailedAcquire函数和名字描述一样,就是判断一个结点应该park(阻塞)还是自旋在一次失败获取锁之后。

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /**
             * 位于SINGAL状态,如果他被取消或者释放锁,会通知后续节点
             * 也就是这里的node,所以node可以安心park
             */
            return true;
        if (ws > 0) {
            /**
             * 前驱节点已经被中断或者等待超时
             * 所以node节点需要把这些前驱节点移除阻塞队列
             * 返回false,不需要park
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /**
             * 节点状态肯定是0或者PROPAGATE,这时候需要让节点在进行一次尝试获取锁,不应该park
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

  1. 获取独占锁,可响应中断
    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

在doAcquireInterruptibly函数检测到线程中断的时候,直接抛出异常,其他前面的逻辑都是一样的

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        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;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException(); //会抛出中断异常,响应中断
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
  1. 获取独占锁,超时返回,可响应中断
     * 1. 成功获取到锁,短路
     * 2. 获取锁失败,尝试等待nanosTimeout这么久,超时返回
     * 响应中断和超时,其他逻辑和acquire几乎相同
     */
    public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }

doAcquireNanos函数中对超时事件做出了响应

    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; // help GC
                    failed = false;
                    return true;
                }
                nanosTimeout = deadline - System.nanoTime();
                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);
        }
    }

  1. 释放独占锁
    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) {
        int ws = node.waitStatus;
        if (ws < 0) //CAS修改头节点状态
            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)//从tail开始倒着找,直到node停止
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);//唤醒找到的符合条件的节点
    }
  1. 获取共享锁
    /**
     * tryAcquireShared留给用户重写获取共享锁方法
     * 返回值负数表示获取失败
     * 0表示获取成功,但是后续节点无法获取成功
     * 正数表示获取成功且后续节点也可能获取成功
     */
    public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }

doAcquireShared函数

    private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);//加入一个共享模式的线程节点
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) { //获取共享锁成功
                    	//在这里进行共享锁的扩散,独占锁没有
                        setHeadAndPropagate(node, r);//将head设置为node,p是node的前驱
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

setHeadAndPropagate函数很关键,在这里对共享锁进行扩散

    private void setHeadAndPropagate(Node node, int propagate) {
        Node h = head; // Record old head for check below
        setHead(node);
        /**
         * 1.tryAcquire返回值是正数,表示后续节点仍然可以获取共享锁,唤醒后续节点
         * 2.头结点为空或者状态是负,也唤醒后续节点
         * 这里采用了保守的策略,虽然可能唤醒导致不必要的唤醒,但是大多数节点很快都会被唤醒
         * 所以影响不大
         */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            /**
             * 如果后继节点是共享的,或者是出现了空,都会执行唤醒的操作
             */
            if (s == null || s.isShared())
                doReleaseShared(); //唤醒节点
        }
    }

doReleaseShared函数在这里具体的节点状态检测和唤醒

    private void doReleaseShared() {
        for (;;) {
            Node h = head;
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) { //如果节点的状态是SINGAL,且CAS成功就唤醒该节点
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    unparkSuccessor(h); //前面已经有提到了
                }
                //如果节点的状态是0,尝试CAS设置为PROPAGTE,以便下次共享锁传播
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            //如果其他线程修改了头结点,那么在执行一次唤醒的逻辑
            if (h == head)                   // loop if head changed
                break;
        }
    }
  1. 获取共享锁,可响应中断
    与独占锁相同,响应中断只是在判断线程出现中断的时候抛出异常,不再赘述
  2. 获取共享锁,可响应中断,超时返回
    与独占锁相同,超时响应只是在自旋获取锁的时候,检测是否超时,超时直接返回,不再赘述
  3. 共享锁释放
    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared(); //调用的doReleaseShared函数在共享锁获取的时候,已经解释过,他们调用的都是同一个函数
            return true;
        }
        return false;
    }

AQS的核心代码其实就是acquire和release函数,分别有独占模式和共享模式两种,模板方法都是final的,不可被重写,而且调用逻辑已经被写好了,我们开发自定义同步组件只需要重写tryAcquire和tryRelease方法等即可。

猜你喜欢

转载自blog.csdn.net/XH413235699/article/details/89033465