zoukankan      html  css  js  c++  java
  • 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,表示释放成功。

  • 相关阅读:
    2020牛客暑期多校训练营(第二场)G-Greater and Greater bitset
    2020牛客暑期多校训练营(第二场)H Happy Triangle 线段树
    平衡树——splay
    动态规划之状态压缩
    动态规划入门理解
    快速幂入门
    最小生成树初步
    线性筛素数
    最短路径—迪杰斯特拉算法入门
    并查集初步
  • 原文地址:https://www.cnblogs.com/blackmlik/p/12952701.html
Copyright © 2011-2022 走看看