关于java:别走这里有个笔记图文讲解-AQS-一起看看-AQS-的源码……图文较长

47次阅读

共计 13133 个字符,预计需要花费 33 分钟才能阅读完成。

前言

AbstractQueuedSynchronizer 形象队列同步器,简称 AQS。是在 JUC 包上面一个十分重要的根底组件,JUC 包上面的并发锁 ReentrantLock CountDownLatch 等都是基于 AQS 实现的。所以想进一步钻研锁的底层原理,十分有必要先理解 AQS 的原理。

公众号:liuzhihangs,记录工作学习中的技术、开发及源码笔记;时不时分享一些生存中的见闻感悟。欢送大佬来领导!

介绍

先看下 AQS 的类图构造,以及源码正文,有肯定的大略理解之后再从源码动手,一步一步钻研它的底层原理。

” 源码正文

提供了实现阻塞锁和相干同步器依附先入先出(FIFO)期待队列(信号量,事件等)的框架。此类中设计了一个对大多数基于 AQS 的同步器有用的原子变量来示意状态(state)。子类必须定义 protected 办法来批改这个 state,并且定义 state 值在对象中的具体含意是 acquired 或 released。思考到这些,在这个类中的其余办法能够实现所有排队和阻塞机制。子类能够放弃其余状态字段,但只能应用办法 getState、setState 和 compareAndSetState 以原子形式更新 state。

子类应被定义为用于实现其关闭类的同步性能的非公共外部辅助类。类 AbstractQueuedSynchronizer 没有实现任何同步接口。相同,它定义了一些办法,如 acquireInterruptibly 能够通过具体的锁和相干同步器来调用适当履行其公共办法。

此类反对独占模式和共享模式。在独占模式下,其余线程不能获取胜利,共享模式下能够(但不肯定)获取胜利。此类不“了解”,在机械意义上这些不同的是,当共享模式获取胜利,则下一个期待的线程(如果存在)也必须确定它是否可能获取。线程在不同模式下的期待共享雷同的 FIFO 队列。通常状况下,实现子类只反对其中一种模式,但同时应用两种模式也能够,例如 ReadWriteLock。仅共享模式不须要定义反对未应用的模式的办法的子类。

这个类中定义了嵌套类 AbstractQueuedSynchronizer.ConditionObject,可用于作为一个 Condition 由子类实现,并应用 isHeldExclusively 办法阐明以后线程是否以独占形式进行,release()、getState() acquire() 办法用于操作 state 原子变量。

此类提供检查和监督外部队列的办法,以及相似办法的条件对象。依据须要进应用以用于它们的同步机制。

要应用这个类用作同步的根底上,须要从新定义以下办法,如应用,通过检查和或批改 getState、setState 或 compareAndSetState 办法:

tryAcquire
tryRelease
tryAcquireShared
tryReleaseShared
isHeldExclusively

通过下面的正文能够得出大略的印象:

  1. 外部依附先入先出(FIFO)期待队列。
  2. 存在 state 示意状态信息。state 值只能用 getState、setState 和 compareAndSetState 办法以原子形式更新。
  3. 反对独占模式和共享模式,但具体须要子类实现具体反对哪种模式。
  4. 嵌套 AbstractQueuedSynchronizer.ConditionObject 能够作为 Condition 由子类实现。
  5. 子类须要从新定义 tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared、isHeldExclusively 办法。

队列节点 Node

Node 节点,蕴含以下元素:

元素 含意
prev 上一个节点
next 下一个节点
thread 持有线程
waitStatus 节点状态
nextWaiter 下一个处于 CONDITION 状态的节点

组合成期待队列则如下:

上面是期待队列节点的 Node 属性:

static final class Node {
    // 节点正在共享模式下期待的标记
    static final Node SHARED = new Node();
    // 批示节点正在以独占模式期待的标记
    static final Node EXCLUSIVE = null;

    // 批示线程已勾销
    static final int CANCELLED =  1;
    // 批示后续线程须要唤醒
    static final int SIGNAL    = -1;
    // 批示线程正在期待条件
    static final int CONDITION = -2;
    // 批示下一次 acquireShared 应该无条件流传
    static final int PROPAGATE = -3;
    
    /**
     * 状态字段,仅应用以下值
     * SIGNAL -1:以后节点开释或者勾销时,必须 unpark 他的后续节点。* CANCELLED 1:因为超时(timeout)或中断(interrupt),该节点被勾销。节点永远不会来到此状态。特地是,具备勾销节点的线程永远不会再次阻塞。* CONDITION -2:该节点目前在条件队列。但它不会被用作同步队列节点,直到转移,转移时的状态将被设置为 0。* PROPAGATE -3:releaseShared 应该被流传到其余节点。* 0:都不是
     * 值以数字示意以简化应用,大多数时候能够查看符号(是否大于 0)以简化应用
     */
    volatile int waitStatus;

    // 上一个节点
    volatile Node prev;

    // 下一个节点
    volatile Node next;

    // 节点持有线程
    volatile Thread thread;

    // 链接下一个期待条件节点,或非凡值共享
    Node nextWaiter;

    // 节点是否处于 共享状态 是,返回 true
    final boolean isShared() {return nextWaiter == SHARED;}

    // 返回前一个节点,应用时 前一个节点不能为空
    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;
    }
}

在 Node 节点中须要重点关注 waitStatus

  1. 默认状态为 0;
  2. waitStatus > 0 (CANCELLED 1) 阐明该节点超时或者中断了,须要从队列中移除;
  3. waitStatus = -1 SIGNAL 以后线程的前一个节点的状态为 SIGNAL,则以后线程须要阻塞(unpark);
  4. waitStatus = -2 CONDITION -2:该节点目前在条件队列;
  5. waitStatus = -3 PROPAGATE -3:releaseShared 应该被流传到其余节点,在共享锁模式下应用。

理解完 Node 的构造之后,再理解下 AQS 构造,并从罕用办法动手,逐渐理解具体实现逻辑。

AbstractQueuedSynchronizer

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {

    // 期待队列的头,提早初始化。除了初始化,它是仅经由办法 setHead 批改。留神:如果头存在,其 waitStatus 保障不会是 CANCELLED 状态
    private transient volatile Node head;

    // 期待队列的尾部,提早初始化。仅在批改通过办法 ENQ 增加新节点期待。private transient volatile Node tail;

    // 同步状态 
    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);
    }

}

在 AQS 中主要参数为:

参数 含意
head 期待队列头
tail 期待队列尾
state 同步状态

通过正文理解到,在 AQS 里次要分为两种操作模式,别离是:独占模式、共享模式,上面别离从两个不同的角度去剖析源码。

操作 含意
acquire 以独占模式获取,疏忽中断。通过调用至多一次施行 tryAcquire,在胜利时返回。否则,线程排队,可能反复查封和解封,调用 tryAcquire 直到胜利为止。这种办法能够用来实现办法 Lock.lock。
release 以独占模式开释。通过畅通一个或多个线程,如果实现 tryRelease 返回 true。这种办法能够用来实现办法 Lock.unlock。
acquireShared 获取在共享模式下,疏忽中断。通过至多一次第一调用实现 tryAcquireShared,在胜利时返回。否则,线程排队,可能反复查封和解封,调用 tryAcquireShared 直到胜利为止。
releaseShared 以共享模式开释。通过畅通一个或多个线程,如果实现 tryReleaseShared 返回 true。

无论是共享模式还是独占模式在这外面都会用到 addWaiter 办法,将以后线程及模式创立排队节点。

独占模式

获取独占资源 acquire

public final void acquire(int arg) {
    // tryAcquire 尝试获取 state,获取失败则会退出到队列
    if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();}

在独占模式下会尝试获取 state,当获取失败时会调用 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)。

  1. tryAcquire(arg),尝试获取 state 这块由子类本人实现,不同的子类逻辑不同,这块在介绍子类代码时会阐明。
  2. 获取 state 失败后,会进行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg),这块代码能够拆分为两块:addWaiter(Node.EXCLUSIVE),acquireQueued(node, arg)。
  3. addWaiter(Node.EXCLUSIVE) 返回的是以后新创建的节点。
  4. acquireQueued(node, arg) 线程获取锁失败,应用 addWaiter(Node.EXCLUSIVE) 放入期待队列,而 acquireQueued(node, arg) 应用循环,一直的为队列中的节点去尝试获取资源,直到获取胜利或者被中断。

总结获取资源次要分为三步:

  1. 尝试获取资源
  2. 入队列
  3. 出队列

尝试获取资源 tryAcquire(arg),由子类实现,那上面则着手别离剖析 入队列 出队列

入队列:addWaiter(Node.EXCLUSIVE)

应用 addWaiter(Node.EXCLUSIVE) 办法将节点插入到队列中,步骤如下:

  1. 依据传入的模式创立节点
  2. 判断尾节点是否存在,不存在则须要应用 enq(node) 办法初始化节点,存在则间接 尝试 插入尾部。
  3. 尝试 插入尾部时应用 CAS 插入,避免并发状况,如果插入失败,会调用 enq(node) 自旋直到插入。
private Node addWaiter(Node mode) {Node node = new Node(Thread.currentThread(), mode);
    // 定位到队列开端的 node
    Node pred = tail;
    if (pred != null) {
        // 新节点的上一个节点 指向尾节点
        node.prev = pred;
        // 应用 CAS 设置尾节点,tail 如果等于 pred 则更新为 node
        if (compareAndSetTail(pred, node)) {
            // 更新胜利则将 pred 的下一个节点指向 node
            pred.next = node;
            return node;
        }
    }
    // 尾节点没有初始化,或竞争失败,自旋
    enq(node);
    return node;
}

/**
 * tailOffset 也就是成员变量 tail 的值
 * 此处相当于:比拟 tail 的值和 expect 的值是否相等,相等则更新为 update
 */
private final boolean compareAndSetTail(Node expect, Node update) {return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
private final boolean compareAndSetHead(Node update) {return unsafe.compareAndSwapObject(this, headOffset, null, update);
}

private Node enq(final Node node) {for (;;) {
        Node t = tail;
        // 尾节点为空 须要初始化头节点,此时头尾节点是一个
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            // 不为空 循环赋值
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

看完代码和正文必定还是有点含糊,当初用图一步一步进行阐明。

因为依据 初始尾节点是否为空 分为两种状况,这里应用两幅图:

  1. 第一幅为第一次增加节点,此时 head 会提早初始化;
  2. 第二幅图为曾经存在队列,进行插入节点;
  3. 留神看代码,enq 办法返回的是 之前的尾节点
  4. addWaiter 办法 返回的是 以后插入的新创建的节点

节点增加到队列之后,返回以后节点,而下一步则须要调用办法 acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 一直的去获取资源。

出队列:acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

办法会通过循环不断尝试获取拿到资源,直到胜利。代码如下:


final boolean acquireQueued(final Node node, int arg) {
    // 是否拿到资源
    boolean failed = true;
    try {
        // 中断状态
        boolean interrupted = false;
        // 有限循环
        for (;;) {
            // 以后节点之前的节点
            final Node p = node.predecessor();
            // 前一个节点是头节点,阐明以后节点是 头节点的 next 即实在的第一个数据节点(因为 head 是虚构节点)// 而后再尝试获取资源
            if (p == head && tryAcquire(arg)) {
                // 获取胜利之后 将头指针指向以后节点
                setHead(node); 
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // p 不是头节点,或者 头节点未能获取到资源(非偏心状况下被别的节点抢占)// 判断 node 是否要被阻塞,if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {if (failed)
            cancelAcquire(node);
    }
}
  1. 一直获取本节点的上一个节点是否为 head,因为 head 是虚构节点,如果以后节点的上一个节点是 head 节点,则以后节点为 第一个数据节点
  2. 第一个数据节点一直的去获取资源,获取胜利,则将 head 指向以后节点;
  3. 以后节点不是头节点,或者 tryAcquire(arg) 失败(失败可能是非偏心锁)。这时候须要判断前一个节点状态决定 以后节点是否要被阻塞(前一个节点状态是否为 SIGNAL)。
/**
 * 依据上一个节点的状态,判断以后线程是否应该被阻塞
 * SIGNAL -1:以后节点开释或者勾销时,必须 unpark 他的后续节点。* CANCELLED 1:因为超时(timeout)或中断(interrupt),该节点被勾销。节点永远不会来到此状态。特地是,具备勾销节点的线程永远不会再次阻塞。* CONDITION -2:该节点目前在条件队列。但它不会被用作同步队列节点,直到转移,转移时的状态将被设置为 0。* PROPAGATE -3:releaseShared 应该被流传到其余节点。* 0:都不是
 *
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 前一个节点的期待状态
    int ws = pred.waitStatus;
    // 前一个节点须要 unpark 后续节点
    if (ws == Node.SIGNAL)
        return true;
    // 以后节点处于勾销状态
    if (ws > 0) {
        do {
            // 将勾销的节点从队列中移除
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 设置前一个节点为 SIGNAL 状态
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}

shouldParkAfterFailedAcquire 办法中,会判断前一个节点的状态,同时勾销在队列中以后节点后面有效的节点。

再持续浏览 出队列 acquireQueued 办法,发现有一个 finally 会判断状态后执行 cancelAcquire(node);,也就是下面流程图中上面的红色方块。

cancelAcquire(Node node)

final boolean acquireQueued(final Node node, int arg) {
    // 是否拿到资源
    boolean failed = true;
    try {
        // 省略
        // 在 finally 会将以后节点置为勾销状态
    } finally {if (failed)
            cancelAcquire(node);
    }
}


private void cancelAcquire(Node node) {
    // 节点不存在 间接返回
    if (node == null)
        return;

    // 勾销节点关联线程
    node.thread = null;

    // 跳过曾经勾销的节点,获取以后节点之前的无效节点
    Node pred = node.prev;
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    // 获取以后节点之前的无效节点的下一个节点
    Node predNext = pred.next;

    // 以后节点设置为勾销
    node.waitStatus = Node.CANCELLED;

    // 以后节点如果是尾节点,则将最初一个无效节点设置为尾节点,并将 predNext 设置为空
    if (node == tail && compareAndSetTail(node, pred)) {compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        // pred 不是头节点(node 的上一个无效节点 不是 head) &&(pred 的状态是 SIGNAL ||  pred 的状态设置为 SIGNAL 胜利)&& pred 的绑定线程不为空
        if (pred != head && 
        ((ws = pred.waitStatus) == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) && 
        pred.thread != null) {
            // 以后节点的后继节点
            Node next = node.next;
            // 后继节点不为空 且 状态无效 将 pred 的 后继节点设置为 以后节点的后继节点
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            // node 的上一个无效节点 是 head,或者其余状况 唤醒以后节点的下一个无效节点
            unparkSuccessor(node);
        }

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

private void unparkSuccessor(Node node) {

    // 判断以后节点状态
    int ws = node.waitStatus;
    if (ws < 0)
        // 将节点状态更新为 0 
        compareAndSetWaitStatus(node, ws, 0);

    // 下一个节点,个别是下一个节点应该就是须要唤醒的节点,即颁发证书。Node s = node.next;
    // 大于 0  CANCELLED:线程已勾销
    // 然而有可能 后继节点 为空或者被勾销了。if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾节点开始遍历,直到定位到 t.waitStatus <= 0 的节点
        // 定位到后并不会进行,会继续执行,相当于找到最开始的那个须要唤醒的节点
        // t.waitStatus <= 0:SIGNAL(-1 后续线程须要开释)//                     CONDITION(-2 线程正在期待条件)//                     PROPAGATE(-3 releaseShared 应该被流传到其余节点)for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 定位到须要唤醒的节点后 进行 unpark
    if (s != null)
        LockSupport.unpark(s.thread);
}

流程剖析:

  1. 找到以后节点的前一个非有效节点 pred;
  2. 以后节点如果是尾节点,则将最初一个无效节点设置为尾节点,并将 predNext 设置为空;
  3. pred 不是头节点 &&(pred 的状态是 SIGNAL || pred 的状态设置为 SIGNAL 胜利)&& pred 的绑定线程不为空;
  4. 其余状况。

上面别离画图:

Q: 通过图能够看进去,只操作了 next 指针,然而没有操作 prev 指针,这是为什么呢?

A:出队列:acquireQueued(addWaiter(Node.EXCLUSIVE), arg) 办法中,shouldParkAfterFailedAcquire 办法会判断前一个节点的状态,同时勾销在队列中以后节点后面有效的节点。这时候会移除之前的有效节点,此处也是为了避免指向一个曾经被移除的节点。同时保障 prev 的稳固,有利于从 tail 开始遍历列表,这块在 unparkSuccessor(node); 中也能够看到是从后往前表里列表。

Q: unparkSuccessor(Node node) 为什么从后往前遍历?

A:

addWaiter(Node.EXCLUSIVE) 插入新节点时,应用的是 尾插法,看红框局部,此时有可能还未指向 next。

Q: node.next = node; 这块导致 head 不是指向最新节点,链表不就断了么?
A: acquireQueued 办法介绍中,外面有个循环,会一直尝试获取资源,胜利之后会设置为 head。并且在 shouldParkAfterFailedAcquire 中也会革除以后节点前的有效节点。

开释独占资源 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。这种办法能够用来实现办法 Lock.unlock。

  1. tryRelease(arg) 操作开释资源,同样是由子类实现,前面介绍子类时会进行阐明。返回 true 阐明资源当初曾经没有线程持有了,其余节点能够尝试获取;
  2. 开释胜利,且 head != null && h.waitStatus != 0, 会继续执行 unparkSuccessor(h);
  3. 这块会看到 只有 tryRelease(arg) 操作开释资源胜利,前面无论执行是否胜利,都会返回 true,unparkSuccessor(h) 相当于只是附加操作。

共享模式

获取共享资源 acquireShared
public final void acquireShared(int arg) {
    // 小于 0 示意获取资源失败
    if (tryAcquireShared(arg) < 0)
        doAcquireShared(arg);
}

private void doAcquireShared(int arg) {
    // 增加到节点 此处是共享节点
    final Node node = addWaiter(Node.SHARED);
    // 依据是否拿到资源 判断是否须要勾销
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            // 返回前一个节点
            final Node p = node.predecessor();
            if (p == head) {
                // 再次尝试获取共享资源
                int r = tryAcquireShared(arg);
                // 示意获取胜利
                if (r >= 0) {
                    // 设置以后节点为头节点 并尝试唤醒后续节点
                    setHeadAndPropagate(node, r);
                    // 开释头节点 GC 会回收
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {if (failed)
            cancelAcquire(node);
    }
}
  1. tryAcquireShared(arg),尝试获取资源,这块由子类实现;
  2. 返回值分为 3 种:

    1. 小于 0: 示意失败;
    2. 等于 0: 示意共享模式获取资源胜利,但后续的节点不能以共享模式获取胜利;
    3. 大于 0: 示意共享模式获取资源胜利,后续节点在共享模式获取也可能会胜利,在这种状况下,后续期待线程必须查看可用性。
  3. 在失败后会应用 doAcquireShared(arg); 一直获取资源;
  4. final Node node = addWaiter(Node.SHARED); 同样会创立节点;
  5. 在循环中一直判断前一个节点如果是 head,则尝试获取资源;
  6. 在共享模式下获取到资源后会应用 setHeadAndPropagate(node, r); 设置头节点,同时唤醒后续节点。
设置头节点,并流传唤醒后续节点
// node 是以后节点
// propagate 是 前一步 tryAcquireShared 的返回值 进来时 >=0
// 大于 0: 示意共享模式获取资源胜利,后续节点在共享模式获取也可能会胜利,在这种状况下,后续期待线程必须查看可用性。private void setHeadAndPropagate(Node node, int propagate) {
    // 记录下以后头节点
    Node h = head; // Record old head for check below
    // 设置传入 node 为头节点
    setHead(node);
    // 判断条件,唤醒后续节点
    // propagate > 0 有后续资源
    // h == null 旧的头节点 因为后面 addWaiter,必定不会为空,应该是避免 h.waitStatus < 0 空指针的写法
    // (h = head) == null 以后的 头节点,再判断状态
    // waitStatus < 0 后续节点就须要被唤醒
    if (propagate > 0 || h == null || h.waitStatus < 0 ||
        (h = head) == null || h.waitStatus < 0) {
        Node s = node.next;
        // 后续节点为共享,则须要唤醒
        if (s == null || s.isShared())
            doReleaseShared();}
}
doReleaseShared() 开释共享资源

private void doReleaseShared() {
    // 循环
    for (;;) {
        // 从头开始
        Node h = head;
        // 判断队列是否为空,就是刚初始化
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // SIGNAL(-1 后续线程须要开释)if (ws == Node.SIGNAL) {
                // 将期待状态更新为 0 如果失败,会循环
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue;            // loop to recheck cases
                // 唤醒后续节点,同时将以后节点设置为 勾销
                unparkSuccessor(h);
            }
            // 如果状态是 0 则会更新状态为 PROPAGATE
            // PROPAGATE(-3 releaseShared 应该被流传到其余节点)else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue;                // loop on failed CAS
        }
        // 判断头节点有没有变动,有变动 是因为竞争,别的线程获取到了锁,会持续循环
        // 没有变动间接完结
        if (h == head)                   // loop if head changed
            break;
    }
}
  1. 从头节点开始进行,如果 h != null && h != tail 阐明队列不是空或者刚初始化;
  2. 节点状态为 SIGNAL(-1)阐明后续线程须要开释;
  3. 会更改以后节点状态,胜利后唤醒后续节点,失败则持续循环;
  4. 节点状态如果是 0 则更新为 PROPAGATE,会将状态流传。
开释共享资源 releaseShared
public final boolean releaseShared(int arg) {if (tryReleaseShared(arg)) {
        // 开释共享资源
        doReleaseShared();
        return true;
    }
    return false;
}

以共享模式开释。通过开释一个或多个线程,如果实现 tryReleaseShared 返回 true。

总结

Q: AQS 到底是什么?
A: AQS 外部提供了一个先入先出(FIFO)双向期待队列,外部依附 Node 实现,并提供了在 独占模式 共享模式 下的出入队列的公共办法。而对于状态信息 state 的定义是由子类实现。tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared 等尝试获取资源操作都是由子类进行定义和实现的。而 AQS 中提供了子类获取资源之后的相干操作,包含节点 Node 的出入队列,自旋获取资源等等。

Q: AQS 获取资源失败后会如何操作?
A: 线程获取资源失败后,会放到期待队列中,在队列中会一直尝试获取资源(自旋),阐明线程只是进入期待状态,前面还是能够再次获取资源的。

Q: AQS 期待队列的数据结构是什么?
A: CLH 变体的先入先出(FIFO)双向期待队列。(CLH 锁是一个自旋锁。能确保无饥饿性。提供先来先服务的公平性。是一种基于链表的可扩大、高性能、偏心的自旋锁,申请线程仅仅在本地变量上自旋,它一直轮询前驱的状态,如果发现前驱开释了锁就完结自旋。)

Q: AQS 期待队列中的节点如何获取获取和开释资源的?
A: 能够看下 独占模式 中的讲述过程,通过代码梳理。

本文别离从 独占模式 共享模式 介绍的 AQS 根本逻辑,并通过源码和作图了解基本思路。然而并没有对须要子类实现的业务逻辑做介绍。这块会在前面介绍 ReentrantLockCountDownLatch 等子类的时候做介绍。

正文完
 0