AQS的源码简答分析-AQS结构

AQS是什么?

在 Lock 中,用到了一个同步队列 AQS,全称AbstractQueuedSynchronizer,它是一个同步工具也是 Lock 用来实现线程同步的核心组件。如果你搞懂了 AQS,那么 J.U.C 中绝大部分的工具都能轻松掌握。

AQS的两种功能

从使用层面来说,AQS 的功能分为两种:独占和共享
独占锁,每次只能有一个线程持有锁,比如 J.U.C包下的的 ReentrantLock 就是以独占方式实现的互斥锁
共 享 锁 , 允 许 多 个 线 程 同 时 获 取 锁 , 并 发 访 问 共 享 资 源 , 比 如 J.U.C包下的ReentrantReadWriteLock

AQS的内部实现

从整体认识AQS

我们下看一下设计者对AbstractQueuedSynchronizer的整体介绍

/**
 * Provides a framework for implementing blocking locks and related
 * synchronizers (semaphores, events, etc) that rely on
 * first-in-first-out (FIFO) wait queues.  This class is designed to
 * be a useful basis for most kinds of synchronizers that rely on a
 * single atomic {@code int} value to represent state. Subclasses
 * must define the protected methods that change this state, and which
 * define what that state means in terms of this object being acquired
 * or released.
 * */

我们通过这段话可以分析出设计考虑要素
1.锁的实现依赖一个先进先出的等待队列
2.依赖一个原子的属性值代表状态,通过一个受保护的方法来更改状态,从而代表锁的获取或者释放

类结构

我们先分析一下AbstractQueuedSynchronizer类的结构

	// node是一个静态内部类,我们知道AQS是一个队列,队列里面每个节点就是一个Node
	static final class Node {
		...
	}
	/**
     * Head of the wait queue, lazily initialized.  Except for
     * initialization, it is modified only via method setHead.  Note:
     * If head exists, its waitStatus is guaranteed not to be
     * CANCELLED.
     */
     // 代表AQS等待队列的头,只能通过setHead方法来设置修改,而且,如果head节点存在,则这个等待状态一定不是CANCELLED,至于CANCELLED我们后面文章中分析
    private transient volatile Node head;
	/**
     * Tail of the wait queue, lazily initialized.  Modified only via
     * method enq to add new wait node.
     */
     // 代表AQS等待队列的末尾,只能通过enq方法来往最后添加一个节点
    private transient volatile Node tail;
	 /**
     * The synchronization state.
     */
     // 用这个字段代表锁的状态
    private volatile int state;
	/**
     * Returns the current value of synchronization state.
     * This operation has memory semantics of a {@code volatile} read.
     * @return current state value
     */
	// 对状态的修改,final修饰的,有点类似于被volatile修饰的变量的写操作
    protected final int getState() {
        return state;
    }
	/**
     * Sets the value of synchronization state.
     * This operation has memory semantics of a {@code volatile} write.
     * @param newState the new state value
     */
     // 对状态的修改,final修饰的,有点类似于被volatile修饰的变量的写操作
    protected final void setState(int newState) {
        state = newState;
    }
	/**
     * Atomically sets synchronization state to the given updated
     * value if the current state value equals the expected value.
     * This operation has memory semantics of a {@code volatile} read
     * and write.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that the actual
     *         value was not equal to the expected value.
     */
     //提供一个CAS实现的原子方法来对状态进行修改
    protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

我们可以看到AQS里面的结构:
1.维护了一个colatile修饰的state变量来代表锁的状态
2.维护了一个元素是node节点的队列
3.存在head(头结点)和tail(尾节点)

对AbstractQueuedSynchronizer类的属性了解后,我们着重看一下这个静态内部类Node的结构,其代码如下

static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        // 是一种共享锁的标识,比如我们说的读锁,或者像CountDownLatch的实现就是用的SHARE
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        // 是一种独占锁的标识,比如我们说的ReentrantLock
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        // 表示等待超时后的状态
        static final int CANCELLED =  1;
        /** waitStatus value to indicate successor's thread needs unparking */
        // 线程的等待状态 表示后继线程需要被唤醒
        static final int SIGNAL    = -1;
        /** waitStatus value to indicate thread is waiting on condition */
        // 代表受condition条件控制的时候,比如调用了await方法
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
         // 共享锁的一种传递机制,我们唤醒一个锁,会继续唤醒下一个锁,这样一直唤醒所有的共享锁
        static final int PROPAGATE = -3;

        /**
         * Status field, taking on only the values:
         *   SIGNAL:     The successor of this node is (or will soon be)
         *               blocked (via park), so the current node must
         *               unpark its successor when it releases or
         *               cancels. To avoid races, acquire methods must
         *               first indicate they need a signal,
         *               then retry the atomic acquire, and then,
         *               on failure, block.
         *   CANCELLED:  This node is cancelled due to timeout or interrupt.
         *               Nodes never leave this state. In particular,
         *               a thread with cancelled node never again blocks.
         *   CONDITION:  This node is currently on a condition queue.
         *               It will not be used as a sync queue node
         *               until transferred, at which time the status
         *               will be set to 0. (Use of this value here has
         *               nothing to do with the other uses of the
         *               field, but simplifies mechanics.)
         *   PROPAGATE:  A releaseShared should be propagated to other
         *               nodes. This is set (for head node only) in
         *               doReleaseShared to ensure propagation
         *               continues, even if other operations have
         *               since intervened.
         *   0:          None of the above
         *
         * The values are arranged numerically to simplify use.
         * Non-negative values mean that a node doesn't need to
         * signal. So, most code doesn't need to check for particular
         * values, just for sign.
         *
         * The field is initialized to 0 for normal sync nodes, and
         * CONDITION for condition nodes.  It is modified using CAS
         * (or when possible, unconditional volatile writes).
         */
         /**
         *   SIGNAL:     当前节点的后继节点处于等待状态时,如果当前节点的同步状态被释放或者取消,
         *               必须唤起它的后继节点
         *         
         *   CANCELLED:  一个节点由于超时或者中断需要在CLH队列中取消等待状态,被取消的节点不会再次等待
         *               
         *   CONDITION:  当前节点在等待队列中,只有当节点的状态设为0的时候该节点才会被转移到同步队列
         *               
         *   PROPAGATE:  下一次的共享模式同步状态的获取将会无条件的传播
 
         * waitStatus的初始值时0,使用CAS来修改节点的状态
         */
        volatile int waitStatus;

        /**
         * Link to predecessor node that current node/thread relies on
         * for checking waitStatus. Assigned during enqueuing, and nulled
         * out (for sake of GC) only upon dequeuing.  Also, upon
         * cancellation of a predecessor, we short-circuit while
         * finding a non-cancelled one, which will always exist
         * because the head node is never cancelled: A node becomes
         * head only as a result of successful acquire. A
         * cancelled thread never succeeds in acquiring, and a thread only
         * cancels itself, not any other node.
         */
         // 指向上一个节点的指针
        volatile Node prev;

        /**
         * Link to the successor node that the current node/thread
         * unparks upon release. Assigned during enqueuing, adjusted
         * when bypassing cancelled predecessors, and nulled out (for
         * sake of GC) when dequeued.  The enq operation does not
         * assign next field of a predecessor until after attachment,
         * so seeing a null next field does not necessarily mean that
         * node is at end of queue. However, if a next field appears
         * to be null, we can scan prev's from the tail to
         * double-check.  The next field of cancelled nodes is set to
         * point to the node itself instead of null, to make life
         * easier for isOnSyncQueue.
         */
         // 指向下一个节点的指针
        volatile Node next;

        /**
         * The thread that enqueued this node.  Initialized on
         * construction and nulled out after use.
         */
         // 每个node其实封装的是一个线程
        volatile Thread thread;

        /**
         * Link to next node waiting on condition, or the special
         * value SHARED.  Because condition queues are accessed only
         * when holding in exclusive mode, we just need a simple
         * linked queue to hold nodes while they are waiting on
         * conditions. They are then transferred to the queue to
         * re-acquire. And because conditions can only be exclusive,
         * we save a field by using special value to indicate shared
         * mode.
         */
         // 链接到下一个节点的等待条件,或特殊的值SHARED
        Node nextWaiter;

        /**
         * Returns true if node is waiting in shared mode.
         */
         // 一种判断是不是共享锁的方法
        final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * Returns previous node, or throws NullPointerException if null.
         * Use when predecessor cannot be null.  The null check could
         * be elided, but is present to help the VM.
         *
         * @return the predecessor of this node
         */
         // 获取前一个node节点
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }
		
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

通过node的数据结构我们可以看出来:
1.每个node是对一个Thread线程的封装
2.node存在两个指针,prev和next分别指向上一个节点和下一个节点
3.node中每个节点也会记录状态

其实分析到这里,我们可以很清楚的知道AQS结构了,其实它并没有多神秘,就是一个双向队列
在这里插入图片描述

队列添加元素(enq方法)

初始化状态,队列为空的情况下,head和tail都是null
在这里插入图片描述
首次添加元素为例,调用enq(node)方法
第一次循环

	/**
     * Inserts node into queue, initializing if necessary. See picture above.
     * @param node the node to insert
     * @return node's predecessor
     */
    // 将node元素放到队列里面去
    private Node enq(final Node node) {
    	// 自旋
        for (;;) {
       		// 把tai节点赋给t
            Node t = tail;
            // 如果t==null,表示此时tail为null,表示此时队列中没有元素,需要初始化队列
            if (t == null) { // Must initialize
            // new Node()创建一个空的node节点
            // 通过CAS将这个node节点赋给head
                if (compareAndSetHead(new Node()))
                // tail赋值,此时的队列状态应该为图1队列初始化状态
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

第一次循环后的队列应该是下图这样的,但是注意,队列中并没有这个node节点,其实这一次的循环只是为了给head和tail赋值,让他们不等于null
在这里插入图片描述
第二次循环

// 将node元素放到队列里面去
    private Node enq(final Node node) {
    	// 自旋
        for (;;) {
       		// 把tai节点赋给t,此时tail和head都指向一个空的node节点
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            // 第二次循环,会进入到这个地方,将node.prev指向t
                node.prev = t;
                // 通过CAS设置tail节点为node
                if (compareAndSetTail(t, node)) {    // 2
                // t.next指向node,node是我们要添加进去的节点
                    t.next = node;                   // 3
                    // 返回t节点,结束
                    return t;
                }
            }
        }
    }

第二次循环后的结果如下图
在这里插入图片描述
所以在多次调用enq()方法之后,AQS队列应该是如下图展示
在这里插入图片描述

队列删除元素(release()方法)

调用release方法其实就是解锁的过程,此方法应该在具体的锁实现中通过unlock方法调用。其实下面的方法需要结合具体的锁去分析,我们只是简单看一下源码

	/**
     * 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}
     */
     // 我们看参数arg,为什么要传入一个int类型的参数呢?因为锁是允许重入的,lock多少次,就要unlock多少次,通过次数来记录释放几次锁
    public final boolean release(int arg) {
    // tryRelease()这是个模板方法,再具体的锁实现里面实现了此方法的逻辑;其实这个方法就是把锁状态次数减去arg次,然后判断锁状态是不是0
        if (tryRelease(arg)) {
        // 把head节点赋值给h
            Node h = head;
            // 判断head节点不是null,并且waitStatus!=0,就去调用unparkSuccessor方法唤醒后续节点
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
private void unparkSuccessor(Node node) {
         // 判断当前node的状态,ws<0,表示是处于signal状态,是可以被唤醒的
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
         //得到 head 节点的下一个节点
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
        //如果下一个节点为 null 或者 status>0 表示 cancelled 状态.
		//通过从尾部节点开始扫描,找到距离 head 最近的一个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方法唤醒指定线程
            LockSupport.unpark(s.thread);
    }

为什么在释放锁的时候是从 tail 进行扫描?
我觉得有必要单独拿出来说一下,我们再回到 enq那个方法、。在标注部分的代码来看一个新的节点是如何加入到链表中的

  1. 将新的节点的 prev 指向 tail
  2. 通过 cas 将 tail 设置为新的节点,因为 cas 是原子操作所以能够保证线程安全性
  3. t.next=node;设置原 tail 的 next 节点指向新的节点
private Node enq(final Node node) {
    	// 自旋
        for (;;) {
            Node t = tail;
            if (t == null) { 
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	// 1 将新的节点的 prev 指向 tail
                node.prev = t;
                // 2 通过 cas 将 tail 设置为新的节点,因为 cas 是原子操作所以能够保证线程安全性
                if (compareAndSetTail(t, node)) { 
                // 3 设置原 tail 的 next 节点指向新的节点
                    t.next = node;
                    return t;
                }
            }
        }
    }

在这里插入图片描述
在 cas 操作之后,t.next=node 操作之前。 存在其他线程调用 unlock 方法从 head开始往后遍历,由于 t.next=node 还没执行意味着链表的关系还没有建立完整。就会导致遍历到 t 节点的时候被中断。所以从后往前遍历,一定不会存在这个问题

那通过这个队列是怎么实现获得锁排队和释放锁的呢,我们从具体的锁分析,下一节ReentrantLock具体分析

本文是综合自己的认识和参考各类资料(书本及网上资料)编写,若有侵权请联系作者,所有内容仅代表个人认知观点,如有错误,欢迎校正; 邮箱:1354518382@qq.com 博客地址:https://blog.csdn.net/qq_35576976/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值