AQS的架构思想,及代码浅析

A其实本来这篇文章一直纠结要不要写,想写又觉得自己水平不够,但是不写的话,后面的ReentrantLock又是基于AQS的,可以说是整个Java并发编程中JUC包中最核心的部分,所以还是打算写一下,水平有限,本篇文章只是对AQS比较浅的探讨,不会太过深入源码,更多的是去理解AQS的整个过程

什么是AQS

AQS的全称是AbstractQueuedSynchronizer,译名抽象队列同步器。可以说是整个Java并发编程包JUC的底层实现,AQS是一种采用CAS+自旋的方式,然后采用一个双向链表组成的队列,结合一个state来实现的同步器。很多JUC包,比如ReentrantLock、Semaphore、CountDownLatch、CyclicBarrier等并发类均是继承AQS,实现AQS的模板方法,来实现的。

框架和整体思想

用个人的理解来解读一下这个图,AQS的核心就是一个标记为volatile的state状态值+一个队列,,这个锁是否被抢占到则是用这个state来标记的,因此把state设置为volatile的目的也是保证对所有的线程可见,我们假设有三个线程去抢占锁,此时线程1抢占成功了,就会把state值设置为1,然后其他的没有抢到的线程则会被封装成一个Node进入到这个阻塞队列中。等待持有锁的线程释放之后,会唤醒离头结点最近的那个节点对应的线程。继续尝试抢占state。由这个过程分析,其实整个AQS都是基于CAS,而CAS又是基于UnSafe类中的一些方法,来保证同时只有一个线程来修改state。

 protected final boolean compareAndSetState(int expect, int update) {
        // See below for intrinsics setup to support this
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

上图大概的说明了一下AQS的整个流程。总结一下就是:对于AQS来说,加锁就是对state的状态值进行+1 如果是可重入的锁就反复加1,释放锁则是对state状态值-1.当state为0的时候,代表没有被其他线程持有,大于0的时候代表已经被其他线程持有了,需要进入到阻塞队列中等待释放锁后的唤醒。

代码层面

AQS(AbstractQueuedSynchronizer)是Java并发工具的基础,采用乐观锁,通过CAS与自旋轻量级的获取锁。
下面简单分析一下他的核心实现:

  • 核心属性
  /**
         * The synchronization state.
         * 同步状态,0代表锁没有被占用,大于0代表锁被占用且表示锁被重入的次数
         */
        private volatile int state;
        /**
        *存储当前获取锁的线程,继承自AbstractOwnableSynchronizer
        */
        private transient Thread exclusiveOwnerThread;
  • 队列实现
  /**
         * 对首,只是一个标志位,不存储线程
         */
        private transient volatile Node head;

        /**
         * 队尾,新增加的等待线程入队节点
         */
        private transient volatile Node tail;
  • Node节点
  /**
        *当前节点所代表的线程
        */
        volatile Thread thread;

        // 双向链表,每个节点需要保存自己的前驱节点和后继节点的引用
        volatile Node prev;
        volatile Node next;

        // 线程所处的等待锁的状态,初始化时,该值为0
        volatile int waitStatus;
        static final int CANCELLED =  1;
        static final int SIGNAL    = -1;
        static final int CONDITION = -2;
        static final int PROPAGATE = -3;

        /**
        * 用于条件队列与共享锁
        */
        Node nextWaiter;
  • 队列

    AQS中的队列是双向链表,依赖于Node节点中的prev和next属性,队列中存储了等待获取锁的集合。队列中有head和tail节点,头节点不存储等待锁线程。

对于AQS获取锁和释放锁这一块,我结合ReentrantLock来说明。本篇只谈一下AQS的基本架构和思想

获取锁

我们一般都是这么使用ReentrantLock获取锁的:

//非公平锁
ReentrantLock lock = new ReentrantLock();
lock.lock();

lock方法:

    public void lock() {
        sync.lock();
    }

Sync为ReentrantLock里面的一个内部类,它继承AQS(AbstractQueuedSynchronizer),它有两个子类:公平锁FairSync和非公平锁NonfairSync。 ReentrantLock里面大部分的功能都是委托给Sync来实现的,同时Sync内部定义了lock()抽象方法由其子类去实现,默认实现了nonfairTryAcquire(int acquires)方法,可以看出它是非公平锁的默认实现方式。下面我们看非公平锁的lock()方法:

    final void lock() {
        //尝试获取锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            //获取失败,调用AQS的acquire(int arg)方法
            acquire(1);
    }

首先会第一次尝试快速获取锁,如果获取失败,则调用acquire(int arg)方法,该方法定义在AQS中,如下:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  1. tryAcquire(int arg)
    由子类实现,获取锁。
  2. addWaiter(Node mode)
    获取锁失败后,将等待线程封装成Node加入等待队列,由AQS实现。
  3. acquireQueued(final Node node, int arg)
    在队列中如果其前驱节点是头节点,就循环获取锁,获取锁成功就返回。
    如果其前驱不是头节点,或者是头节点但是获取锁失败,挂起当前线程。由AQS实现。
  4. selfInterrupt()
    自我中断,当获取锁的时候,发生中断时记录下来,推迟到抢锁结束后中断线程

这个方法首先调用tryAcquire(int arg)方法,在AQS中讲述过,tryAcquire(int arg)需要自定义同步组件提供实现,非公平锁实现如下:

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    final boolean nonfairTryAcquire(int acquires) {
        //当前线程
        final Thread current = Thread.currentThread();
        //获取同步状态
        int c = getState();
        //state == 0,表示没有该锁处于空闲状态
        if (c == 0) {
            //获取锁成功,设置为当前线程所有
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //线程重入
        //判断锁持有的线程是否为当前线程
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        return false;
    }

该方法主要逻辑:首先判断同步状态state == 0 ?,如果是表示该锁还没有被线程持有,直接通过CAS获取同步状态,如果成功返回true。如果state != 0,则判断当前线程是否为获取锁的线程,如果是则获取锁,成功返回true。成功获取锁的线程再次获取锁,这是增加了同步状态state。

释放锁

获取同步锁后,使用完毕则需要释放锁,ReentrantLock提供了unlock释放锁:

    public void unlock() {
        sync.release(1);
    }

unlock内部使用Sync的release(int arg)释放锁,release(int arg)是在AQS中定义的:

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

与获取同步状态的acquire(int arg)方法相似,释放同步状态的tryRelease(int arg)同样是需要自定义同步组件自己实现:

    protected final boolean tryRelease(int releases) {
        //减掉releases
        int c = getState() - releases;
        //如果释放的不是持有锁的线程,抛出异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //state == 0 表示已经释放完全了,其他线程可以获取同步状态了
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

只有当同步状态彻底释放后该方法才会返回true。当state == 0 时,则将锁持有线程设置为null,free= true,表示释放成功。

posted @ 2020-05-24 21:12  穿黑风衣的牛奶  阅读(716)  评论(0编辑  收藏  举报