美文网首页
AQS原理简介

AQS原理简介

作者: 平凡人笔记 | 来源:发表于2020-06-19 15:16 被阅读0次

    AQS简介

    • 原名 AbstractQueuedSynchronizer 即队列同步器 是构建锁和其他同步组件的基础框架(如ReentrantLock、ReentrantReadWriteLock、Semaphore、CountDownLatch)

    • AQS解决了子类实现同步器时涉及当的大量细节问题,例如获取同步状态、FIFO同步队列 自定义同步器在实现时只需要实现共享资源state的获取与释放方式即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了,所以使用AQS不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题

    • 只能在一个时刻发生阻塞,从而降低上下文切换的开销,提高了吞吐量

    • AQS通过内置的FIFO同步队列来完成资源获取线程的排队工作,如果当前线程获取同步状态失败(锁)时,AQS则会将当前线程以及等待状态等信息构造成一个节点(Node)并将其加入同步队列,同时会阻塞当前线程,当同步状态释放时,则会把节点中的线程唤醒,使其再次尝试获取同步状态

    深入源码 看看有哪些方法

    • getState()
    返回同步状态的当前值
    • setState(int newState)
    设置当前同步状态
    • compareAndSetState(int expect, int update)
    使用CAS设置当前状态,该方法能够保证状态设置的原子性

    自定义同步器主要实现以下几个方法

    • tryAcquire(int arg)
    独占式获取同步状态,获取同步状态成功后,其他线程需要等待该线程释放同步状态才能获取同步状态
    • tryRelease(int arg)
    独占式释放同步状态
    • tryAcquireShared(int arg)
    共享式获取同步状态,返回值大于等于0则表示获取成功,否则获取失败
    • tryReleaseShared(int arg)
    共享式释放同步状态
    • tryReleaseShared(int arg)
    当前同步器是否在独占式模式下被线程占用,一般该方法表示是否被当前线程所独占
    • acquire(int arg)
    独占式获取同步状态,如果当前线程获取同步状态成功,则由该方法返回,否则,将会进入同步队列等待,该方法将会调用可重写的tryAcquire(int arg)方法
    • acquireInterruptibly(int arg)
    与acquire(int arg)相同,但是该方法响应中断,当前线程为获取到同步状态而进入到同步队列中,如果当前线程被中断,则该方法会抛出InterruptedException异常并返回
    • tryAcquireNanos(int arg,long nanos)
    超时获取同步状态,如果当前线程在nanos时间内没有获取到同步状态,那么将会返回false,已经获取则返回true
    • acquireShared(int arg)
    共享式获取同步状态,如果当前线程未获取到同步状态,将会进入同步队列等待,与独占式的主要区别是在同一时刻可以有多个线程获取到同步状态
    • acquireSharedInterruptibly(int arg)
    共享式获取同步状态,响应中断
    • tryAcquireSharedNanos(int arg, long nanosTimeout)
    共享式获取同步状态,增加超时限制
    • release(int arg)
    独占式释放同步状态,该方法会在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒
    • releaseShared(int arg)
    共享式释放同步状态

    CLH

    CLH同步队列是一个FIFO双向队列,AQS依赖它来完成同步状态的管理,
    当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,
    当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。

    在CLH同步队列中,一个节点表示一个线程,它保存着
    线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),其数据结构如下

    同步队列的数据结构

    入列

    CHL这种链表式结构入列,无非就是tail指向新节点、新节点的前驱节点指向当前最后的节点,当前最后一个节点的next指向当前节点
    • addWaiter(Node node)
    将当前线程加入到等待队列的队尾,并返回当前线程所在的结点

    addWaiter(Node node)先通过快速尝试设置尾节点,如果失败,则调用enq(Node node)方法设置尾节点

    此方法用于将node加入队尾,该方法核心就是通过CAS自旋的方式来设置尾节点,知道获得预期的结果即添加节点成功,当前线程才会返回

    出列

    CLH同步队列遵循FIFO(先进先出),首节点的线程释放同步状态后,
    将会唤醒它的后继节点(next),而后继节点将会在获取同步状态成功时将自己设置为首节点,这个过程非常简单,
    head执行该节点并断开原首节点的next和当前节点的prev即可,
    注意在这个过程是不需要使用CAS来保证的,因为只有一个线程能够成功获取到同步状态。

    同步状态的获取与释放

    独占式同步状态获取

    如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响
    • tryAcquire:去尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法由自定义同步组件自己实现(通过state的get/set/CAS),该方法必须要保证线程安全的获取同步状态。

    • addWaiter:如果tryAcquire返回FALSE(获取同步状态失败),则调用该方法将当前线程加入到CLH同步队列尾部,并标记为独占模式。

    • acquireQueued:当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;如果在整个等待过程中被中断过,则返回true,否则返回false。

    • selfInterrupt:如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上。

    当前线程会一直尝试获取同步状态,当然前提是只有其前驱节点为头结点才能够尝试获取同步状态

    1、保持FIFO同步队列原则。

    2、头节点释放同步状态后,将会唤醒其后继节点,后继节点被唤醒后需要检查自己是否为头节点。

    这段代码主要检查当前线程是否需要被阻塞,具体规则如下:

    [1]如果当前线程的前驱节点状态为SINNAL,则表明当前线程需要被阻塞,
    调用unpark()方法唤醒,直接返回true,当前线程阻塞

    [2]如果当前线程的前驱节点状态为CANCELLED(ws > 0),
    则表明该线程的前驱节点已经等待超时或者被中断了,
    则需要从CLH队列中将该前驱节点删除掉,直到回溯到前驱节点状态 <= 0 ,返回false

    [3]如果前驱节点非SINNAL,非CANCELLED,
    则通过CAS的方式将其前驱节点设置为SINNAL,返回false

    整个流程中,如果前驱结点的状态不是SIGNAL,那么自己就不能被阻塞,
    需要去找个安心的休息点(前驱节点状态 <= 0 ),同时可以再尝试下看有没有机会去获取资源。

    如果 shouldParkAfterFailedAcquire(Node pred, Node node) 方法返回true,则调用parkAndCheckInterrupt()方法阻塞当前线程

    相关文章

      网友评论

          本文标题:AQS原理简介

          本文链接:https://www.haomeiwen.com/subject/hfnvxktx.html