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;
         
  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值