Java多线程五——深入理解AQS实现原理

一、AQS简介

1.1 AQS是什么

AQS全称为 AbstractQueuedSynchronizer ,翻译过来就是抽象队列同步器。AQS是一个用来构建锁和其他同步组件的基础框架,使用AQS可以简单且高效地构造出应用广泛的同步器,例如我们后续会讲到的 ReentrantLockSemaphoreReentrantReadWriteLock FutureTask 等等。

AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时的锁分配机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列 (虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系) 。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。

1.2 资源的共享方式

AQS定义两种资源共享方式:

  • Exclusive(独占):只能有一个线程能执行,如 ReentrantLock。又可分为公平锁和非公平锁:
    • 公平锁:按照线程在队列中的排队顺序,先到者先拿到锁
    • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
  • Share(共享):多个线程可同时执行,如 Semaphore、CountDownLatch 等。

1.3 AQS的设计模式

AQS的设计是使用模板方法设计模式,它将一些方法开放给子类进行重写,而同步器给同步组件所提供模板方法又会重新调用被子类所重写的方法。自定义同步器时需要重写下面几个AQS提供的模板方法:

//该线程是否正在独占资源。只有用到condition才需要去实现它。
isHeldExclusively();
//独占方式。尝试获取资源,成功则返回true,失败则返回false
tryAcquire(int);
//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryRelease(int);
//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryAcquireShared(int);
//共享方式。尝试释放资源,成功则返回true,失败则返回false。
tryReleaseShared(int);

上面的方法均使用 protect 修饰,且默认实现都会抛出 UnsupportedOperationException 异常。这些方法的实现必须是内部线程安全的。AQS类中的其他方法都是 final ,所以无法被其他类覆盖。

二、AQS源码解析

AbstractQueuedSynchronizer 类图如下:

AbstractQueuedSynchronizer 类中有两个内部类,分别为 Node 和 ConditionObject 。Node类上面已经介绍过了,ConditionObject类实现Condition接口,用于实现Condition相关功能。

2.1 CLH队列

上面我们说到,当共享资源被某个线程占有,其他请求该资源的线程会被阻塞,从而进入同步队列。AQS底层的数据结构采用CLH队列,它是一个虚拟的双向队列,即不存在队列的实例,仅存在节点之间的关联关系。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配。AQS内部实现了两个队列,一个同步队列,一个条件队列,同步队列和条件队列都是由一个个Node组成的:

  • Sync queue,即同步队列,是双向链表,包括head结点和tail结点,head结点主要用作后续的调度。当线程获取资源失败之后,就进入同步队列的尾部保持自旋等待,不断判断自己是否是链表的头节点,如果是头节点,就不断参试获取资源,获取成功后则退出同步队列。
  • Condition queue,即条件队列,它是一个单向链表,只有当使用Condition时,才会存在此单向链表,并且可能会有多个Condition queue。

其中节点的类型是AQS的静态内部类Node,代码如下:

static final class Node {
    //模式,分为共享与独占
    //共享模式
    static final Node SHARED = new Node();
    //独占模式
    static final Node EXCLUSIVE = null;

    //节点状态值:1,-1,-2,-3
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;

    //节点状态
    volatile int waitStatus;

    //前驱节点
    volatile Node prev;

    //后继节点
    volatile Node next;

    //节点对应的线程
    volatile Thread thread;

    //下一个等待者
    Node nextWaiter;

    //判断节点是否在共享模式下等待
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    //获取前驱节点,如果为null,则抛异常
    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
    }

    //有参构造函数1
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }

    //有参构造函数2
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

Node节点的状态有如下四种:

  • CANCELLED = 1 :表示当前节点从同步队列中取消,即当前线程被取消
  • SIGNAL = -1 :表示后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行。
  • CONDITION = -2 :表示当前节点在等待condition,也就是在condition queue中。
  • PROPAGATE = -3 :表示下一次共享式同步状态获取将会无条件传播下去
  • 新入队的node的waitStatus是默认值0,即无状态

2.2 AQS属性及构造方法

//序列化版本号
private static final long serialVersionUID = 7373984972572414691L;
//头结点
private transient volatile Node head;
//尾结点
private transient volatile Node tail;
//状态
private volatile int state;
//自旋时间
static final long spinForTimeoutThreshold = 1000L;

//Unsafe实例
private static final Unsafe unsafe = Unsafe.getUnsafe();
//state的内存偏移地址
private static final long stateOffset;
//head的内存偏移地址
private static final long headOffset;
//tail的内存偏移地址
private static final long tailOffset;
//waitStatus的内存偏移地址
private static final long waitStatusOffset;
//next的内存偏移地址
private static final long nextOffset;

//调用Unsafe类的objectFieldOffset方法获取各个属性的内存偏移地址,这些偏移地址可以直接操作内存获取和修改值
static {
    try {
        stateOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
        headOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
        tailOffset = unsafe.objectFieldOffset
            (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
        waitStatusOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("waitStatus"));
        nextOffset = unsafe.objectFieldOffset
            (Node.class.getDeclaredField("next"));

    } catch (Exception ex) { throw new Error(ex); }
}

//构造方法(protected修饰,供子类调用)
protected AbstractQueuedSynchronizer() { }

2.2.1 state 变量

无论是独占锁还是共享锁,本质上都是对AQS内部的一个变量 state 的获取。state是一个原子的int变量,用来表示锁状态、资源数等。

AQS中的共享资源是使用一个 int 成员变量 state 来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改。状态信息通过procted类型的getState,setState,compareAndSetState进行操作。

//同步状态,使用volatile来保证其可见性
private volatile int state;

//获取同步状态
protected final int getState() {
    return state;
}

//设置同步状态
protected final void setState(int newState) {
    state = newState;
}

//原子性地修改同步状态
protected final boolean compareAndSetState(int expect, int update) {
    // See below for intrinsics setup to support this
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

2.3 AQS核心方法详解

AQS中方法众多,总体可分为两大类,独占式共享式地获取和释放锁。下面从这个两方面进行详细讲解。

2.3.1 独占式同步状态获取_不响应中断(acquire()方法)

AQS提供的 AbstractQueuedSynchronizer#acquire 方法是独占式获取同步状态,但是该方法对中断不敏感,也就是说由于线程获取同步状态失败加入到CLH同步队列中,后续对线程进行中断操作时,线程不会从同步队列中移除。代码如下:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

该方法中一共出现了四个方法:

  • tryAcquire() :尝试获取锁,获取成功则设置锁状态并返回true,否则返回false。该方法由自定义同步组件自己去实现,该方法必须要保证线程安全的获取同步状态。
  • addWaiter() :入队,即将当前线程加入到CLH同步队列尾部。
  • acquireQueued() :当前线程会根据公平性原则来进行阻塞等待(自旋),直到获取锁为止;并且返回当前线程在等待过程中有没有中断过。
  • selfInterrupt() :自我中断。

我们来分析一下 acquire() 方法的执行的流程

1)首先调用方法 tryAcquire() 方法尝试获取锁(该方法需要自定义同步组件自己实现,这里先不赘述,后续讲解各个组件时再详细描述),如果成功返回true,整个 acquire() 方法全部返回,获取锁成功;否则获取锁失败,继续往下执行;

2)tryAcquire() 返回false,获取锁失败,则继续执行方法 acquireQueued() ,在此之前会先执行方法 addWaiter() 将当前线程加入到CLH同步队列尾部新入队的node的waitStatus是默认值0);

private Node addWaiter(Node mode) {
    //new一个代表当前线程的新节点,默认为独占模式
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    //保存尾结点
    Node pred = tail;
    //如果尾结点不为空,说明队列已经被初始化了,直接入队尾
    if (pred != null) {
        //将新节点的前驱连接到原尾结点上
        node.prev = pred;
        //修改尾结点为当前新节点
        if (compareAndSetTail(pred, node)) {
            //原尾结点的后继指向新节点
            pred.next = node;
            //返回新节点
            return node;
        }
    }
    //如果尾结点为空(表示队列未初始化)或者CAS操作失败(有其他线程抢先修改了尾结点信息),则会调用该方法进行初始化队列后自旋,直到入队成功,或者直接自旋入队操作
    enq(node);
    return node;
}

//CAS方式修改尾结点
private final boolean compareAndSetTail(Node expect, Node update) {
    return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

//自旋操作,确保节点成功入队
private Node enq(final Node node) {
    //无限循环(也叫自旋),目的是确保节点能够成功入队
    for (;;) {
        //保存尾结点,这里每次循环都会重新取一次最新的尾结点
        Node t = tail;
        //如果尾结点为空,则需要先初始化
        if (t == null) { // Must initialize
            //new一个空Node作为头结点和尾结点
            if (compareAndSetHead(new Node()))
                tail = head;
        } 
        //如果尾结点不为空,说明队列已经初始化了,再次尝试入队,入队成功直接返回原尾结点,否则继续下一次循环
        else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

从上面代码我们可以看出,将node加入到队尾的时候,如果queue队列还没初始化,会先将head和tail初始化一个哨兵空节点,然后再循环CAS来将node加入到队尾。所以说,queue的对应的头一直是空节点(不是null),头节点是一个哨兵节点,queue的准确结构如下:

从上述代码我们可以看出,入队有三个步骤:

  • 保存当前尾节点tail为 t,将 node.prev 设置为 t ;
  • CAS将 tail 由指向 t 改为指向 node ;
  • 将 t.next 设置为 node 。自此完成了新节点的入队。

这三个操作并不是原子的。当出现多个线程同时入队的时候,每个线程都能完成步骤1,也就是成功设置完prev,但是CAS并不一定会成功,就会出现一种尾分叉的现象。

3)入队操作结束后执行方法 acquireQueued() ,该方法是一个自旋的过程,即每个线程进入同步队列中,都会自旋地观察自己是否满足条件且获取到同步状态,是则可以从自旋过程中退出,否则继续自旋下去;

final boolean acquireQueued(final Node node, int arg) {
    //表示执行过程中是否发生异常,初始值为true
    boolean failed = true;
    try {
        //表示当前线程是否被中断过,初始值为false
        boolean interrupted = false;
        //自旋过程
        for (;;) {
            //获取当前节点的前驱节点
            final Node p = node.predecessor();
            //如果前驱节点是头结点,表示可以进行获取锁,此时就会调用方法tryAcquire,否则无法参与获取锁,获取锁成功后进入if内
            if (p == head && tryAcquire(arg)) {
                //此时当前节点获取到了锁,设置头结点为当前节点,同时该节点的Thread的引用设置为空
                setHead(node);
                //清除p的引用
                p.next = null; // help GC
                //执行过程未发生异常,failed标记为false
                failed = false;
                //返回中断标志
                return interrupted;
            }
            //当获取同步状态失败后,判断是否需要park
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        //如果执行过程发生任何异常则执行
        if (failed)
            cancelAcquire(node);
    }
}

//设置node为头节点
private void setHead(Node node) {
    //头结点引用设置为node
    head = node;
    //node的thread引用置为空
    node.thread = null;
    //node的prev引用置为空
    node.prev = null;
}

自旋获取同步状态,如果获取同步状态失败,会执行方法 shouldParkAfterFailedAcquire() 判断是否需要进行park() 挂起,代码如下:

//判断是否需要对当前线程进行park
//pred:当前线程节点的前驱节点
//node:当前线程节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //获取当前线程节点的前驱节点的状态
    int ws = pred.waitStatus;
    //如果状态是SIGNAL,表示node可以被安全park
    if (ws == Node.SIGNAL)
        //返回true,即node可以被安全park
        return true;
    //如果状态大于0,即为CANCELLED,表示pred节点的线程被取消,则需要找到从后往前找到第一个状态不为CANCELLED的结点作为node的前驱
    if (ws > 0) {
        //从后往前找到第一个态不为CANCELLED的结点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } 
    // 表示节点状态为为PROPAGATE(-3)或者是0(表示无状态)或者是CONDITION(-2,表示此节点在condition queue中)
    else {
        //比较并设置前驱结点的状态为SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

节点状态标志:

  • CANCELLED = 1 :表示当前节点从同步队列中取消,即当前线程被取消。
  • SIGNAL = -1 :表示后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行。
  • CONDITION = -2 :表示当前节点在等待condition,也就是在condition queue中。
  • PROPAGATE = -3 :表示下一次共享式同步状态获取将会无条件传播下去。

如果 shouldParkAfterFailedAcquire() 返回false,说明当前节点 node 的前驱节点 pred 放弃锁了(CANCELLED状态),将 pred 剔除后,自己在队列中的位置前移,node很有可能就来到了queue的第二个节点(head是哨兵空节点),此时在下次自旋会调用 tryAcquire() 尝试着抢夺一下锁,当抢夺不到锁的时候,才会再次尝试执行 shouldParkAfterFailedAcquire() ,也就是将线程挂起。如果 shouldParkAfterFailedAcquire() 返回 true,则执行方法 parkAndCheckInterrupt() 来park当前线程,代码如下:

// 进行park操作并且返回该线程是否被中断
private final boolean parkAndCheckInterrupt() {
    //阻塞当前线程
    LockSupport.park(this);
    //被唤醒后返回当前线程是否被中断过,会清除中断标记
    return Thread.interrupted();
}

返回结果表示当前线程是否被中断过。

最后我们来看一下 acquireQueued() 方法中finally块中的 cancelAcquire() 方法(只有在自旋过程中发生异常时才执行,因为此时failed为true),该方法的作用就是取消当前线程对资源的获取,即设置该结点的状态为CANCELLED,代码如下:

private void cancelAcquire(Node node) {
    // Ignore if node doesn't exist
    //如果节点为空直接返回
    if (node == null)
        return;
	
    //将当前节点的thread引用置空
    node.thread = null;

    // Skip cancelled predecessors
    //保存当前节点的前驱
    Node pred = node.prev;
    //找到node前驱结点中第一个状态不为CANCELLED状态的结点
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // predNext is the apparent node to unsplice. CASes below will
    // fail if not, in which case, we lost race vs another cancel
    // or signal, so no further action is necessary.
    //获取pred的后继节点
    Node predNext = pred.next;

    // Can use unconditional write instead of CAS here.
    // After this atomic step, other Nodes can skip past us.
    // Before, we are free of interference from other threads.
    //设置当前节点node的状态为CANCELLED
    node.waitStatus = Node.CANCELLED;

    // If we are the tail, remove ourselves.
    //如果当前节点是尾结点,设置尾结点为前驱节点
    if (node == tail && compareAndSetTail(node, pred)) {
        //比较并设置pred结点的next节点为null
        compareAndSetNext(pred, predNext, null);
    } 
    //如果node结点不为尾结点,或者比较设置不成功
    else {
        // If successor needs signal, try to set pred's next-link
        // so it will get one. Otherwise wake it up to propagate.
        int ws;
        //两种情况满足其一,则if成立
        //情况1:pred结点不为头结点,并且pred结点的状态为SIGNAL 
        //情况2:pred结点状态小于等于0,并且比较并设置等待状态为SIGNAL成功,并且pred结点所封装的线程不为空
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            //保存node的后继
            Node next = node.next;
            //如果后继不为空并且后继的状态小于等于0
            if (next != null && next.waitStatus <= 0)
                //比较并设置pred.next = next
                compareAndSetNext(pred, predNext, next);
        } 
        //如果不满足上面任何一种情况,则释放node的前一个结点
        else {
            //释放node的后继结点
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

unparkSuccessor() 方法的作用就是为了释放node节点的后继结点,细节在下面的释放锁时会细讲。

4)最后如果tryAcquire() 返回false获取锁失败,且 acquireQueued() 方法返回true表示当前线程被中断过,需要恢复它的中断标记,所以调用方法 selfInterrupt() 进行自我中断。

【整个acquire方法的主要流程总结如下】

1、执行tryAcquire()方法抢锁,tryAcquire()方法是锁工具自定义实现的。抢锁成功后就直接执行同步区代码。

2、如果抢锁不成功,执行addWaiter()方法:将线程包装为Node,利用自旋的CAS将Node加入到queue队尾中。

3、加入Queue后,会执行accquireQueued()方法自旋地观察自己是否满足条件且获取到同步状态,是则可以从自旋过程中退出,否则继续自旋下去。

3.1、如果线程对应的node处于queue的第二个节点,因为head是哨兵空节点,这个时候会尝试再次抢夺锁;

3.2、如果线程node不满足抢夺锁的条件,会尝试挂起。

3.2.1、如果node的前驱节点prev的waitStatus是Signal则直接会 被挂起;

3.2.2、如果node的前驱节点prev的waitStatus是Cancelled,则会循环将waitStatus为cancelled的节点都剔除出去,这个时候返回到accquireQueued()的循环里,node很有可能会前移到第二个节点,满足抢夺锁的条件,会尝试抢夺锁,如果抢夺失败就将prev的waitStatus改为Signal,然后将线程挂起;

3.2.3、如果node的前继节点prev的waitStatus < 0,说明prev为head或者是新插入的节点,会将其prev的waitStatus设置为Signal,然后返回false,最后拼搏抢夺一次锁。

2.3.2 独占式同步状态释放(release() 方法)

当线程获取同步状态后,执行完相应逻辑后就需要释放同步状态。释放锁的逻辑相对简单一些,代码如下:

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

该方法中包含两个方法

  • tryRelease() :尝试释放锁,成功则返回true,失败则返回false。同样也是由自定义组件自己实现。
  • unparkSuccessor() :释放当前节点的后继结点。

分析一下release方法的执行的流程,大致分为以下两步:

1)调用方法 tryRelease() ,尝试去释放锁;成功则继续执行,否则返回false。

protected boolean tryRelease(int arg) {
    throw new UnsupportedOperationException();
}

从上面我们可以看出,tryRelease 和 tryAcquire 一样,都是需要锁工具自定义实现的。

2)释放锁成功后,如果存在后继节点,则需要调用方法 unparkSuccessor() 唤醒后继节点的线程,代码如下:

private void unparkSuccessor(Node node) {

    //当前节点的状态
    int ws = node.waitStatus;
    //如果状态小于0,设置状态为0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    //获取当前节点的后继
    Node s = node.next;
    //如果下一个结点为空或者下一个节点的等待状态为CANCELLED,需要继续往后找到第一个状态小于等于0的节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        //从尾结点开始往前遍历,找到第一个在node节点后的状态小于等于0的节点,赋值给s
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    //如果s不为空,唤醒s对应的线程
    if (s != null)
        LockSupport.unpark(s.thread);
}

唤醒了之后呢?对应的线程会从 上文获取锁 acquire()#accquireQueue()#parkAndCheckInterrupt() 中挂起线程时的代码开始执行:

private final boolean parkAndCheckInterrupt() {
    //线程被挂起
    LockSupport.park(this);
    //这里这里这里
    return Thread.interrupted();
}

LockSupport.park() :

在AQS的实现中有一个出现了一个park的概念。park即LockSupport.park(Thread) ,它的作用是阻塞当前线程,并且可以调用LockSupport.unpark(Thread)去停止阻塞。它们的实质都是通过UnSafe类使用了CPU的原语。在AQS中使用park的主要作用是,让排队的线程阻塞掉(停止其自旋,自旋会消耗CPU资源),并在需要的时候,可以方便的唤醒阻塞掉的线程。

【整个release方法的主要流程如下图所示】:

2.3.3 独占式同步状态获取_响应中断(acquireInterruptibly()方法)

前面说到的 acquire() 方法对中断不响应。因此为了响应中断,AQS提供了acquireInterruptibly() 方法,该方法在等待获取同步状态时,如果当前线程被中断了,会立刻响应中断抛出异常InterruptedException。代码如下:

public final void acquireInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (!tryAcquire(arg))
        //如果获取锁失败,入队同时响应中断
        doAcquireInterruptibly(arg);
}

由代码可以看出,首先会校验该线程是否已经被中断了,如果是则抛出InterruptedException,否则执行 tryAcquire() 方法(该方法同样由自定义同步器自己实现)获取同步状态;如果获取成功,则直接返回,否则执行 doAcquireInterruptibly() 方法,代码如下:

private void doAcquireInterruptibly(int arg)
    throws InterruptedException {
    //节点入队
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                //如果发现线程被中断了,直接抛异常
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

该方法与 acquireQueued() 方法非常类似,唯一不同之处在于如果发现线程被中断了,直接抛出异常,目的为了响应中断异常

2.3.4 独占式超时获取(tryAcquireNanos()方法)

AQS还提供了一个增强版的获取锁的方法,tryAcquireNanos() 方法不仅可以响应中断,还有超时控制,即当前线程没有在指定时间内获取同步状态则返回失败。代码如下:

public final boolean tryAcquireNanos(int arg, long nanosTimeout)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquire(arg) ||
        doAcquireNanos(arg, nanosTimeout);
}

方法实现与 acquireInterruptibly() 类似,不过当获取锁失败时,会调用方法 doAcquireNanos() ,代码如下:

private boolean doAcquireNanos(int arg, long nanosTimeout)
    throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    //计算超时时间,即最迟获取锁的时间
    final long deadline = System.nanoTime() + nanosTimeout;
    //入队
    final Node node = addWaiter(Node.EXCLUSIVE);
    boolean failed = true;
    try {
        //自旋获取锁
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return true;
            }
            //获取同步状态失败,进行中断和超时判断
            //重新计算需要休眠的时间
            nanosTimeout = deadline - System.nanoTime();
            //如果deadline小于等于当前时间,说明超时了,直接返回false
            if (nanosTimeout <= 0L)
                return false;
            //如果未超时,则进行park操作
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            //中断判断
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

这里的超时控制需要说明一下:

  • 在自旋获取锁之前,先记录一下超时时间 dealine ;
  • 其次判断当前node的前驱节点pred是否为头节点,是则尝试获取锁,如果获取成功直接返回true;
  • 否则会计算 deadline 与当前时间的差值 nanosTimeout ,如果小于等于0,则表示已经到了超时时间,直接返回fasle;
  • 否则进行park判断,除了之前的 shouldParkAfterFailedAcquire() 方法的判断,还有 nanosTimeout 和 spinForTimeoutThreshold 常量的比较:
    • 如果大于常量spinForTimeoutThreshold,则会进行park相应的nanosTimeout 时间;
    • 如果小于等于常量spinForTimeoutThreshold,说明nanosTimeout 值很小(因为常量spinForTimeoutThreshold本身的值就很小),在非常短的时间等待无法做到十分精确,如果这时再次进行超时等待,相反会让nanosTimeout 的超时从整体上面表现得不是那么精确,所以在超时非常短的场景中,AQS会进行无条件的快速自旋。

2.3.5 共享式同步状态获取(acquireShared方法)

AQS提供 acquireShared() 方法共享式获取同步状态:

public final void acquireShared(int arg) {
    //尝试获取锁,小于0表示获取锁失败
    if (tryAcquireShared(arg) < 0)
        //自旋获取同步状态
        doAcquireShared(arg);
}

首先调用 tryAcquireShared()(自定义同步组件自己实现)方法尝试获取同步状态,如果获取成功直接结束,否则调用 doAcquireShared() 方法获取同步状态,代码如下:

private void doAcquireShared(int arg) {
	//入队,此时节点为共享式节点
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //获取当前节点的前驱节点
            final Node p = node.predecessor();
            //如果p是头节点
            if (p == head) {
                //尝试获取锁
                int r = tryAcquireShared(arg);
                //如果r大于等于0,表示获取锁成功
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

代码与之前讲解的非常相似,这里就不再赘述了。

2.3.6 共享式同步状态释放(releaseShared方法)

AQS提供 releaseShared() 方法释放共享式同步状态:

public final boolean releaseShared(int arg) {
    //尝试释放锁
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
}

调用方法 tryReleaseShared() 尝试释放锁,如果失败直接返回false,否则调用方法 doReleaseShared() ,代码如下:

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        //如果队列存在排队的节点
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            if (ws == Node.SIGNAL) {
                //CAS设置不成功则不断循环
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                //CAS操作成功后释放后继节点
                unparkSuccessor(h);
            }
            else if (ws == 0 &&
                     !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        //队列不存在排队的节点,直接结束自旋
        if (h == head)                   // loop if head changed
            break;
    }
}
  • CANCELLED = 1 :表示当前节点从同步队列中取消,即当前线程被取消。
  • SIGNAL = -1 :表示后继节点的线程处于等待状态,如果当前节点释放同步状态会通知后继节点,使得后继节点的线程能够运行。
  • CONDITION = -2 :表示当前节点在等待condition,也就是在condition queue中。
  • PROPAGATE = -3 :表示下一次共享式同步状态获取将会无条件传播下去。
  • 新入队的node的waitStatus是默认值0,即无状态 。

2.3.7 共享式获取同步状态响应中断(acquireSharedInterruptibly方法)

public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    if (tryAcquireShared(arg) < 0)
        doAcquireSharedInterruptibly(arg);
}

与独占式类似,不过调用的方法是 doAcquireSharedInterruptibly()

private void doAcquireSharedInterruptibly(int arg)
    throws InterruptedException {
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

添加中断的相应,直接抛出异常。

2.3.8 共享式超时获取同步状态(tryAcquireSharedNanos方法)

public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
    throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    return tryAcquireShared(arg) >= 0 ||
        doAcquireSharedNanos(arg, nanosTimeout);
}

调用方法 doAcquireSharedNanos()

private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
    throws InterruptedException {
    if (nanosTimeout <= 0L)
        return false;
    final long deadline = System.nanoTime() + nanosTimeout;
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
        for (;;) {
            final Node p = node.predecessor();
            if (p == head) {
                int r = tryAcquireShared(arg);
                if (r >= 0) {
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
            }
            nanosTimeout = deadline - System.nanoTime();
            if (nanosTimeout <= 0L)
                return false;
            if (shouldParkAfterFailedAcquire(p, node) &&
                nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

  • 12
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值