运用

咱们这儿借助ReentrantLock来搞清楚AQS的完成原理。

lock

这个办法便是开端获取锁运转的入口,在这个办法的完成中,交给了sync目标来获取锁。

public void lock() {
    sync.acquire(1);
}
private final Sync sync;
// Sync目标是一个ReentrantLock完成的内部抽象类,详细的完成又分为了公正版本与非公正两种
abstract static class Sync extends AbstractQueuedSynchronizer {}
// 在ReentrantLock的无参结构器中,默许运用的完成便是非公正锁的完成
public ReentrantLock() {
    sync = new NonfairSync();
}
// 也能够经过带参数的结构器来运用公正锁
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Sync

由于公正锁FairSyncNonfairSync的差别主要在tryAcquire办法上,别的逻辑都是相同的,因而咱们就直接看Sync和AQS中的完成。

acquire

办法完成如下,来自AQS的完成:

// 首要会调用 tryAcquire 和 acquireQueued 办法,假如2个办法都回来true的话,
// 那么才会调用自行中止的逻辑
if (!tryAcquire(arg) &&
    acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
    selfInterrupt();

tryAcquire办法就会由于公正锁和非公正锁的差异,有2种不同的完成,首要来看看非公正锁的完成,也便是ReentrantLock的默许策略。

NonfairSync.tryAcquire

这个办法会直接调用并回来 Sync 完成的 nonfairTryAcquire(acquires)办法。

Sync类中的完成

// 这儿的参数 acquires = 1
final boolean nonfairTryAcquire(int acquires) {
    // 获取当时调用者的线程目标
    final Thread current = Thread.currentThread();
    // 获取AQS中定义的state值,这个state值是AQS的中心之一
    int c = getState();
    // 在ReentrantLock的完成中,state就表明当时是否有线程持有锁,0代表没有线程持有锁,
    // 当时访问的线程就能够持续履行代码,假如大于0则表明当时持有锁的线程的数量。
    // 由于ReentrantLock属于可重入锁,因而,这个值会>=1
    if (c == 0) {
        // 能进来就表明当时没有线程持有锁,那么测验用CAS获取锁
        if (compareAndSetState(0, acquires)) {
            // 获取锁成功,那么将当时线程设置到AQS中的当时线程中
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 假如当时持有锁的便是自己,那么就代表是锁的重入
    else if (current == getExclusiveOwnerThread()) {
        // 累计持有锁的次数
        int nextc = c + acquires;
        // 这儿就阐明了,state能够设置的最大值便是Int.MAX_VALUE,
        // 当处于MAX_VALUE的时候再加1,那么Int数字的最高位就会变成1,符号位为负
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        // 更新新的state值
        setState(nextc);
        return true;
    }
    return false;
}

AQS中的完成

private volatile int state;
// 当时持有锁的线程目标
private transient Thread exclusiveOwnerThread;
protected final void setExclusiveOwnerThread(Thread thread) {
    exclusiveOwnerThread = thread;
}

小结一下,非公正锁tryAcquire办法便是先看看有没有线程持有锁,没有的话自己就经过CAS的办法测验获取一下锁,假如获取锁成功或许是自己重入,那么tryAcquire办法就会回来true,acquire办法中的条件判别就会直接回来false,lock办法完毕,线程持续支持下面的代码。

FairSync.tryAcquire

下面来看看公正锁的完成,大体的逻辑跟非公正的是相同的。

FairSync中的完成

// 这儿的参数 acquire = 1
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    // 判别当时是不是有线程持有锁
    if (c == 0) {
        // 当时没有线程持有锁就进来
        // 由于是公正锁,那么就要保证只要在当时等候行列为空或许行列中等候的线程
        // 都没有到运转的条件的时候,才测验经过CAS来获取锁。否则就去乖乖排队
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 同非公正锁,锁重入
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

AQS中的完成

// 查看当时AQS等候行列中是否有正在等候的有用线程节点
public final boolean hasQueuedPredecessors() {
    Node h, s;
    // 首要让参数h指向当时行列的头部
    if ((h = head) != null) {
        // 行列不为空
        // 将暂时变量赋值为当时第二个节点
        // 这儿需要简单阐明一下AQS的等候行列的构成,第一个节点是没有事务意义的,
        // 仅仅用作唤醒下一个待履行的线程节点
        if ((s = h.next) == null || s.waitStatus > 0) {
            // 能进来就表明当时行列只要一个头结点,或许第二个节点的状况是已撤销
            // - > 参考阐明1
            // 假如第二个节点不为空,那么就开释这个引证
            s = null; // traverse in case of concurrent cancellation
            // 从后往前遍历,找到距离行列头最近的有用节点
            for (Node p = tail; p != h && p != null; p = p.prev) {
                if (p.waitStatus <= 0)
                    s = p;
            }
        }
        // 假如找到了正在行列中的排队的有用节点而且不是当时访问的线程,那么就回来true
        if (s != null && s.thread != Thread.currentThread())
            return true;
    }
    // 头结点指向NULL,那么阐明行列是空的,直接回来false
    return false;
}

阐明1:这儿就引入了行列节点中的等候状况这个重要的概念,在ReentrantLock中,咱们只需要关注CANCELLEDSIGNAL即可。只要CANCELLED是大于0的,新节点的默许值为0。因而只需等候状况大于0就代表该节点被撤销了。

// 等候行列中节点的等候状况
volatile int waitStatus;
// 当时节点由于等候超时或许被中止了被撤销
static final int CANCELLED =  1;
// 接下来有资历被唤醒取得锁的符号,只要取得了这个符号的节点才干被履行完的线程唤醒
static final int SIGNAL    = -1;

小结一下,公正锁对比非公正锁,在最开端有机会获取锁的时候,会先查看一下当时行列中是否现已有线程在排队等候履行了,假如等候行列中是空的或许没有有用的排队节点,才会获取锁。假如获取锁成功,或许锁重入成功,那么同样会完毕AQS的逻辑,持续履行事务代码。

acquireQueued

上面剖析完在tryAcquire办法中假如成功取得锁的状况,就会完毕AQS的逻辑,接下来就来剖析未能成功取得锁的逻辑,即:acquire办法中条件判别的第二个条件判别。

在AQS中完成

// 这个办法就会将当时线程添加到等候行列中,而且回来操作是否成功,arg便是传入的acquire值,为1
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)

首要经过addWaiter办法构建一个包含线程目标的节点而且添加到行列中

// 这儿的参数为 Node.EXCLUSIVE,表明这是一个排它锁的完成,这儿的值为NULL
private Node addWaiter(Node mode) {
    // 构建一个线程节点
    Node node = new Node(mode);
    // 这儿便是AQS的中心理念了,经过不断的自旋,将线程节点刺进到行列中
    for (;;) {
        // 获取本来的行列的队尾,由于AQS才去的尾插办法
        Node oldTail = tail;
        if (oldTail != null) {
            // 将新刺进的节点指向本来的尾结点
            node.setPrevRelaxed(oldTail);
            // 经过CAS的办法将当时节点设置到线程同享行列的尾部去,这儿要注意,
            // 但凡涉及到多线程操作的特点,都需要经过CAS保证操作的原子性
            if (compareAndSetTail(oldTail, node)) {
                oldTail.next = node;
                // 设置成功,就回来刺进的节点目标;如若不成功,
                // 就表明有别的线程也修改了尾结点,那么就要等下一次循环重试
                return node;
            }
        } else {
            // 没有尾结点阐明行列不存在,那么就进行初始化
            initializeSyncQueue();
        }
    }
}
// 初始化等候行列
private final void initializeSyncQueue() {
    Node h;
    // 仍是经过CAS的办法,给行列初始化一个默许的Node节点,几个重要的特点的初始值如下
    // waitStatus = 0; thread = null
    if (HEAD.compareAndSet(this, null, (h = new Node())))
        tail = h;
}

小结一下,addWaiter办法经过尾插的办法将没有抢到锁的线程封装为Node节点,刺进到AQS的等候行列中。假如行列还未初始化,那么就先初始化行列,行列自带一个无实践意义的头结点。

acquireQueued

在AQS中完成

// arg便是传入的acquire参数,为1;该办法回来值的意义为,线程在等候过程中是否被中止
final boolean acquireQueued(final Node node, int arg) {
    boolean interrupted = false;
    try {
        // 仍是不断的自旋,等候机会进行操作
        for (;;) {
            // 获取新刺进节点的上一个节点
            final Node p = node.predecessor();
            // 假如上一个节点现已是头结点,那么阐明当时就现已轮到当时线程获取锁履行事务了
            // 那么就再次测验抢一次锁
            if (p == head && tryAcquire(arg)) {
                // 能进来就阐明获取锁成功了
                // 那么就将当时线程的节点设置为头结点
                // 在这个办法中,会去除节点中的信息,做一个朴实的头结点
                setHead(node);
                // 将现已没有指向的原头结点的next指为空,等候回收
                p.next = null; // help GC
                // 这儿回来的值为false,由于当时线程并未被堵塞就取得了锁
                return interrupted;
            }
            // 走到这儿阐明当时线程并没有获取到锁,那么就要考虑是否要将线程堵塞了
            if (shouldParkAfterFailedAcquire(p, node))
                interrupted |= parkAndCheckInterrupt();
        }
    } catch (Throwable t) {
        cancelAcquire(node);
        if (interrupted)
            selfInterrupt();
        throw t;
    }
}
private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}
// 线程获取锁失利之后,是否需要将线程堵塞,这儿2个参数,
// pred是新刺进节点的上一个节点,node是新刺进的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    // 获取上一个节点的等候状况
    int ws = pred.waitStatus;
    // 判别上一个节点的状况是不是SIGNAL,只要状况为SIGNAL的才是有用的可指向节点
    if (ws == Node.SIGNAL)
        // 假如上一个节点是SIGNAL状况,那就阐明当时线程能够连接上该节点,然后被挂起了
        return true;
    // 上面咱们提到过,只要节点被撤销了,等候状况才会>0
    if (ws > 0) {
        // 一向往前找,直到找到等候状况<=0的,数据标准的话即找到最近的一个等候状况
        // 为SIGNAL的节点,越过悉数被撤销的节点
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        // 此时pred指向的即第一个合法的线程节点,指向当时新刺进的节点
        pred.next = node;
    } else {
        // 这儿的意思便是上一个节点便是有用节点,那么就将上一个节点的等候状况强制
        // 更新为SIGNAL,即-1。毫无意义的那个头结点也会被设置为SIGNAL状况
        pred.compareAndSetWaitStatus(ws, Node.SIGNAL);
    }
    return false;
}

小结一下,这个办法的意义便是,由于当时线程没有获取到锁资源,因而就需要被堵塞。同时在这个办法中,也会收拾等候行列,将那些现已被撤销的节点从行列中移除。接着便是调用条件判别中的履行办法,将线程挂起堵塞起来。

private final boolean parkAndCheckInterrupt() {
    // 调用了park办法,底层是调用UnSafe类的park办法来完成
    LockSupport.park(this);
    return Thread.interrupted();
}

至此,lock办法的悉数状况都清楚了,假如线程能拿到锁,那就直接完毕lock阶段,要是抢不到锁,那么就进入等候行列中,在进入行列之前,假如发现还有机会获取锁,那么会再次测验获取一次,如若仍是获取不到,那么就以尾插的办法进入等候行列,经过调用LockSupport.park办法,将线程堵塞,等候被唤醒。

unlock

自动调用这个办法今后,就代表关于锁的占用完毕。

在ReentrantLock中完成

public void unlock() {
    sync.release(1);
}

在AQS中完成

public final boolean release(int arg) {
    // 调用tryRelease办法真正完成免除锁
    // 只要state加的一切锁被免除了,那么才会唤醒下一个线程
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

在Sync中完成

// 这儿的releases便是传入的参数1,即假如是重入锁,那么这儿需要解锁屡次
protected final boolean tryRelease(int releases) {
    // 核算state的值,这儿的意义便是state-1
    int c = getState() - releases;
    // 假如当时线程不是持有锁的线程,那么就报错
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    // 锁是否彻底开释完的符号
    boolean free = false;
    // state减到0阐明锁现已彻底开释完了
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 更新state的值
    setState(c);
    // 只要锁被彻底开释完,才回来true
    return free;
}

在AQS中完成

// 唤醒下一个需要锁的线程,这儿的node是头结点
private void unparkSuccessor(Node node) {
    // 取得头结点的等候状况
    int ws = node.waitStatus;
    // 假如头结点是SIGNAL,那么重置为0,由于这个节点现已没有意义了,会被移除
    if (ws < 0)
        node.compareAndSetWaitStatus(ws, 0);
    // 取得头结点后边的待唤醒的节点
    Node s = node.next;
    // 假如这个待唤醒的节点为空或许等候状况不正确,在这儿便是不等于SIGNAL
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 从尾部开端查询,找到合法的待唤醒节点
        for (Node p = tail; p != node && p != null; p = p.prev)
            if (p.waitStatus <= 0)
                s = p;
    }
    if (s != null)
        // 唤醒线程
        LockSupport.unpark(s.thread);
}

cancelAcquire

这个办法供给了撤销线程等候获取锁的功用

在AQS中被完成

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;
    // 经过上面的循环今后pred的值就为等候行列中队尾的一个合法的未被撤销的节点
    // 获取到该合法节点下一个指向的节点
    Node predNext = pred.next;
    // 符号当时被撤销的节点的等候状况为被撤销
    node.waitStatus = Node.CANCELLED;
    // 假如被撤销的节点便是队尾的节点,那么就经过CAS将pred设置为尾结点
    // 就能够扔掉中心那些同样被符号为被撤销的节点,假如有的是
    if (node == tail && compareAndSetTail(node, pred)) {
        // 将pred的下一个节点指向NULL,由于pred现在便是队尾
        pred.compareAndSetNext(predNext, null);
    } else {
        // 这阐明撤销的节点不是尾结点,而是中心的节点
        // 这个值会在下面的条件判别被赋值为上一个节点的等候状况
        int ws;
        // 假如找到的上一个合法节点不是头结点
        // 而且上一个节点的等候状况是SIGNAL,而且将不是SIGNAL状况的负数状况转换为SIGNAL
        // 而且线程不为空
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && pred.compareAndSetWaitStatus(ws, Node.SIGNAL))) &&
            pred.thread != null) {
            // 能进来就阐明被撤销的节点处于中心,那么就要将这个node从行列中越过
            Node next = node.next;
            // 假如被撤销的节点是一个有用的节点,不为空而且状况也是对的
            if (next != null && next.waitStatus <= 0)
                // 那么就将上一个节点指向被撤销节点的下一个节点
                pred.compareAndSetNext(predNext, next);
        } else {
            // 能进入这儿阐明上一个合法节点现已是头结点了,
            // 那么就阐明被撤销的这个节点现已是本来除了头结点以外的最靠前面的节点,
            // 那么被撤销的这个节点其实就等价于头结点了,应该唤醒后边还在等候的线程节点
            // 唤醒下一个被挂起的线程,详细现已剖析过了,这儿就省略了
            unparkSuccessor(node);
        }
        node.next = node;
    }
}