AQS详解

一、 AQS的类图结构

AQS全称AbstractQueuedSynchronizer,即抽象同步队列,是一个基于先进先出(FIFO)等待队列的实现阻塞锁和同步器的框架。

下面看一下AQS的类图结构:

img

AQS的子类在其他类中的引用:

类结构

1. state

在AQS中维持了一个单一的共享状态state,来实现同步器同步。state用volatile修饰,保证多线程中的可见性。

  • getState():获取当前的同步状态

  • setState(int newState):设置当前同步状态

  • compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态设置的原子性

    以上3种方法都是采用final修饰的,不允许子类重写。

相关代码如下 :

  /**
   * 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
   */
  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
   */
  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.
   */
  protected final boolean compareAndSetState(int expect, int update) {
   
      // See below for intrinsics setup to support this
      return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
  }

2. Node

AQS是基于FIFO队列的存储结构,它是以内部类Node节点(储节点)的形式进行存储。这个等待队列是CLH同步队列。

CLH同步队列中,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),**condition队列的后续节点(nextWaiter)**如下图:

img
waitStatus几种状态状态:

img

static final class Node {
   
    /** 共享节点模式下的节点 */
    static final Node SHARED = new Node();
    
    /** 独占模式下的节点 */
    static final Node EXCLUSIVE = null;
    
    /** 取消状态 */
    // 由于超时或中断而导致当前线程(对应同步队列或条件队列中的一个节点)被取消
    // CANCELLED是终态
    // 被取消了的节点对应的线程永远不会阻塞,放弃竞争锁
    static final int CANCELLED =  1;
    
    /** 后继节点的线程处于等待状态,而当前节点的线程如果释放了同步状态或者被取消,将会通知后继节点,使后继节点的线程得以运行 */
    // 当前节点的后继节点通过park操作被阻塞(或将要被阻塞)
    // 因此当前节点在它们释放锁或被取消的时候,需要通过unpark操作唤醒它的后继节点
    // 为了避免竞争(依据等待状态进行筛选,无需全部唤醒),
    // 执行竞争锁的方法(acquire methods)的线程首先需要表明它们需要被唤醒,
    // 如果竞争锁失败,它们就会被阻塞,等待被唤醒
    // 是否需要被唤醒,其实是记录在当前节点的前驱节点的等待状态中
    // 因此SIGNAL表示后继节点需要被唤醒,这一点非常重要!!
    static final int SIGNAL    = -1;
    
    /** waitStatus value to indicate thread is waiting on condition */
    // 当前线程对应的节点处于条件队列中
  	// 在当前线程对应的节点转移到同步队列之前,同步队列不会使用当前线程对应的节点
  	// 在当前线程对应的节点转移到同步队列的时候,等待状态会首先被设置为0
    static final int CONDITION = -2;
    /**
     * 下一次共享式同步状态获取将会无条件地传播下去
     */
    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).
     */
    volatile int waitStatus;

    /**
     * 同步队列中的前驱节点
     */
    volatile Node prev;

    /**
     * 同步队列中的后继节点
     */
    volatile Node next;

    /**
     * 获取同步状态的线程(请求锁或等待Condition的线程)
     */
    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.
     */
    //条件队列的后继节点
    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
     */
    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;
    }
}

二、AQS同步队列(CLH)

1. AQS同步原理

CLH(Craig, Landin, and Hagersten locks) 同步队列 是一个FIFO双向队列,其内部通过节点head和tail记录队首和队尾元素,队列元素的类型为Node。AQS依赖它来完成同步状态state的管理,当前线程如果获取同步状态失败时,AQS则会将当前线程已经等待状态等信息构造成一个节点(Node)并将其加入到CLH同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点唤醒(公平锁),使其再次尝试获取同步状态。 img

2. AQS同步器的结构—入列与出列

2.1 AQS同步器的结构

同步器拥有首节点(head)和尾节点(tail)。同步队列的基本结构如下:

img
​ 图 1.同步队列的基本结构 compareAndSetTail(Node expect,Node update)

2.2 入列

同步队列设置尾节点(未获取到锁的线程加入同步队列)

​ 同步器AQS中包含两个节点类型的引用:一个指向头结点的引用(head),一个指向尾节点的引用(tail),当一个线程成功的获取到锁(同步状态),其他线程无法获取到锁而是被构造成节点(包含当前线程,等待状态)加入到同步队列中等待获取到锁的线程释放锁。

注意:

​ 加入队列的过程,必须要保证线程安全。否则如果多个线程的环境下,可能造成添加到队列等待的节点顺序错误,或者数量不对。因此同步器提供了CAS原子的设置尾节点的方法(保证一个未获取到同步状态的线程加入到同步队列后,下一个未获取的线程才能够加入)。

​ 如下图: tail指向新节点、新节点的prev指向当前最后的节点,当前最后一个节点的next指向当前节点

img 图 2.尾节点的设置 节点加入到同步队列

代码实现可以参考:2.1 独占锁的获取 addWaiter()

2.3 出列

同步队列设置首节点(原头节点释放锁,唤醒后继节点)

​ 同步队列遵循FIFO,头节点是获取锁(同步状态)成功的节点,头节点在释放同步状态的时候,会唤醒后继节点,而后继节点将会在获取锁(同步状态)成功时候将自己设置为头节点。设置头节点是由获取锁(同步状态)成功的线程来完成的,由于只有一个线程能够获取同步状态,则设置头节点的方法不需要CAS保证,只需要将头节点设置成为原首节点的后继节点 ,并断开原头结点的next引用。

​ 如下图,设置首节点:

img

​ 图 3.首节点的设置

代码实现可以参考:2.2 独占锁的释放

三、 AQS锁

1. AQS支持的锁的类别

  • 独占锁:锁在一个时间点只能被一个线程占有。根据锁的获取机制,又分为“公平锁”和“非公平锁”。等待队列中按照FIFO的原则获取锁,等待时间越长的线程越先获取到锁,这就是公平的获取锁,即公平锁。而非公平锁,线程获取的锁的时候,无视等待队列直接获取锁。ReentrantLock和ReentrantReadWriteLock.Writelock是独占锁。

  • 共享锁:同一个时候能够被多个线程获取的锁,能被共享的锁。JUC包中ReentrantReadWriteLock.ReadLock,CyclicBarrier,CountDownLatch和Semaphore都是共享锁。

2. 基于AQS实现锁(独占与共享模式)

AQS中没有实现任何的同步接口,所以一般子类通过继承AQS以内部类的形式实现锁机制。一般通过继承AQS类实现同步器,通过getState、setState、compareAndSetState来监测状态, AQS定义了的一些模板方法,子类只需要覆盖这几个方法即可注意着五个方法并不是抽象方法,因此子类并不是必须全部覆盖这些方法。JUC中实现这些方法的子类有:

  • tryAcquire():独占方式。尝试获取资源,成功则返回true,失败则返回false。在这里插入图片描述

  • tryRelease():独占方式。尝试释放资源,成功则返回true,失败则返回false。在这里插入图片描述

  • tryAcquireShared():共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。在这里插入图片描述

  • tryReleaseShared():共享方式。尝试释放资源,如果释放后允许唤醒后续等待结点返回true,否则返回false。

在这里插入图片描述

  • isHeldExclusively():该线程是否正在独占资源。只有用到condition才需要去实现它。

注意:AQS用到了模板方法设计模式

模板方法模式: 在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤。

独占式:同一时刻仅有一个线程持有同步状态,如ReentrantLock。又可分为公平锁和非公平锁。

  1. 公平锁: 按照线程在队列中的排队顺序,有礼貌的,先到者先拿到锁。
  2. 非公平锁: 当线程要获取锁时,无视队列顺序直接去抢锁,不讲道理的,谁抢到就是谁的。

共享式:多个线程可同时执行,如Semaphore/CountDownLatch等都是共享式的产物。

一般来说,自定义同步器要么是独占方法,要么是共享方式,他们也只需实现tryAcquire-tryRelease、tryAcquireShared-tryReleaseShared中的一种即可。但AQS也支持自定义同步器同时实现独占和共享两种方式,如ReentrantReadWriteLock。

2.1 独占锁的获取

参考: https://www.cnblogs.com/200911/p/6031350.html

调用同步器的acquire(int arg)方法可以获取同步状态,该方法对中断不敏感,即线程获取同步状态失败后进入同步队列,后续对线程进行中断操作时,线程不会从同步队列中移除。

(1) 当前线程实现通过tryAcquire()方法尝试获取锁,获取成功的话直接返回,如果尝试失败的话,进入等待队列排队等待,可以保证线程安全(CAS)的获取同步状态。

(2) 如果尝试获取锁失败的话,构造同步节点(独占式的Node.EXCLUSIVE),通过addWaiter(Node node,int args)方法,将节点加入到同步队列的队列尾部。

(3) 最后调用acquireQueued(final Node node, int args)方法,使该节点以死循环的方式获取同步状态,如果获取不到,则阻塞节点中的线程。acquireQueued方法当前线程在死循环中获取同步状态,而只有前驱节点是头节点的时候才能尝试获取锁(同步状态)( p == head && tryAcquire(arg))。

原因:

1.头结点是成功获取同步状态的节点,而头结点的线程释放锁以后,将唤醒后继节点,后继节点线程被唤醒后要检查自己的前驱节点是否为头结点。

2.维护同步队列的FIFO原则,节点进入同步队列以后,就进入了一个自旋的过程,每个节点(后者说每个线程)都在自省的观察。

2.1.1 acquire()
#java.util.concurrent.locks.AbstractQueuedSynchronizer
/**
 * Acquires in exclusive(互斥) mode, ignoring(忽视) interrupts.  Implemented
 * by invoking at least once {@link #tryAcquire},
 * returning on success.  Otherwise the thread is queued(排队), possibly
 * repeatedly(反复的) blocking and unblocking, invoking {@link
 * #tryAcquire} until success.  This method can be used
 * to implement method {@link Lock#lock}.
 *
 * @param arg the acquire argument.  This value is conveyed(传达) to
 *        {@link #tryAcquire} but is otherwise uninterpreted and
 *        can represent anything you like.
 *        
 *  独占式的获取同步状态      
 *        
 */
public final void acquire(int arg) {
   
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
2.1.2 tryAcquire()

尝试获取锁:tryAcquire方法:如果获取到了锁,tryAcquire返回true,反之,返回false。

#java.util.concurrent.locks.ReentrantLock.FairSync
    protected final boolean tryAcquire(int acquires) {
   
    //获取当前线程
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
   //当前锁没被占用
        if (!hasQueuedPredecessors() &&//1.判断同步队列中是否有节点在等待
            compareAndSetState(0, acquires)) {
   //2.如果上面!1成立,修改state值(表明当前锁已被占用)
            setExclusiveOwnerThread(current);//3.如果2成立,修改当前占用锁的线程为当前线程
            return true;
        }
    }
    else if (current == getExclusiveOwnerThread()) {
   </
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值