AQS框架分析

一. 同步器

多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足某个条件,才能触发线程执行。

而这个共同的语义可以称之为同步器,可以认为几乎 juc ( java.util.concurrent ) 下面所有的锁机制都可以基于同步器定制来实现的。而 juc 的思想是将这些场景抽象出来的语义通过同意的同步框架来支持。

juc 里所有的这些锁机制都是基于 AQS ( AbstractQueuedSynchronizer ) 框架上构建的。具体介绍可以参考 Doug Lea 的论文 The java.util.concurrent Synchronizer Framework

juc_locks_structure

上图中,Lock 的实现类其实都是构建在 AQS 上,之所以没有在图上表示出来,是因为每个 Lock 实现类都持有自己内部类 Sync 的实例,而这个 Sync 是继承自 AQS,之所以要实现不同的 Sync 是和每种 Lock 用途相关。

二. AQS 如何构建同步器

AQS 的核心思想是基于volatile int state这样的 volatile 变量,配合 Unsafe 工具对其原子性的操作来实现对当前锁状态进行修改。同步器内部依赖一个 FIFO 的双向队列来完成资源获取线程的排队工作。

同步器的基本功能:一个同步器至少需要包含两个功能

  1. 获取同步状态

    如果允许,则获取锁;如果不允许就阻塞线程,知道同步状态允许获取

  2. 释放同步状态

    修改同步状态,并且唤醒等待线程

而在作者的论文中,AQS 同步机制同时考虑了如下需求:

  • 独占锁和共享锁两种机制
  • 线程阻塞后,如果需要取消,需要支持中断
  • 线程阻塞后,如果有超时要求,应该支持超时后中断的机制

1. AQS 同步器的结构

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

aqs_struct

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

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

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

aqs_setTail

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

同步队列遵守 FIFO,头节点是获取锁(同步状态)成功的节点,头节点在释放同步状态的时候,会唤醒后继节点,而后继节点将会在获取锁(同步状态)成功的时候将自己设置为头节点。设置头节点是由获取锁(同步状态)成功的线程来完成的。

由于只有一个线程能够获取同步状态,因此设置头节点的方法不需要 CAS 保证。只需要将头节点设置成为设置成为原头节点的后继节点,并断开原头节点的 next 引用,如下图所示:

aqs_setHead

2. 独占锁的获取

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

获取锁的流程:

  • 当前线程实现通过tryAcquire()方法尝试获取锁,获取成功的话直接返回;如果尝试失败的话,进入等待队列排队等待,可以保证线程安全 (CAS) 的获取同步状态
  • 如果尝试获取锁失败的话,构造同步节点(独占式的Node.EXCLUSIVE),通过addWaiter(Node node,int args)方法,将节点加入到同步队列的队列尾部
  • 最后调用acquireQueued(final Node node, int args)方法,使该节点以自旋的方式获取同步状态,如果获取不到,则阻塞节点中的线程。而且只有前驱节点是头节点的时候才能尝试获取锁(同步状态)p == head && tryAcquire(arg)

原因:

  • 头节点是成功获取同步状态的节点,而头节点的线程释放锁以后,将唤醒后继节点,后继节点线程被唤醒后要检查自己的前驱节点是否为头节点
  • 维护同步队列的 FIFO 原则,节点进入同步队列以后,就进入一个自旋的过程,每个节点都在不断观察自己

aqs_getLock

独占锁的获取源码分析

阻塞地获取锁acquire获取锁如果失败则会阻塞线程并加入 CLH(Craig, Landin, and Hagerste) 队列中

public final void acquire(int arg) {
    // tryAcquire 由子类实现本身不会阻塞线程,如果返回 true,则线程继续,
    // 如果返回 false 那么就加入阻塞队列阻塞线程,并等待前继结点释放锁
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))  // 获取失败,则加入阻塞队列
        // acquireQueued返回true,说明当前线程被中断唤醒后获取到锁,
        // 重置其interrupt status为true
        selfInterrupt();
}

非阻塞地获取锁tryAcquire本身不回阻塞线程,如果返回 true 成功就继续;如果返回 false,那么就阻塞线程并加入阻塞队列

// 源码中 AQS 是一个Abstract类,而且 tryAcquire 方法仅仅是抛出一个 UnsupportedOperationException
// 以下源码截取自 ReentrantLock 的公平锁(FairSync) 重写后的 tryAcquire 方法
protected final boolean tryAcquire(int acquires) {
    // 获取当前线程
    final Thread current = Thread.currentThread();
    // 获取“独占锁”的状态,获取父类 AQS 的标志位
    int c = getState();
    // c == 0 意思是锁(同步状态)没有被任何线程获取
    // 1. 当前线程是否是同步队列中头结点Node,如果是的话,则获取该锁,设置锁的状态,并设置锁的拥有者为当前线程
    if (c == 0) {
        if (!hasQueuedPredecessors() &&
            // 修改状态:这里的acquires的值是1,是写死的调用子类的lock的方法的时候传进来的,如果c == 0,compareAndSetState操作会更新成功为1.
            compareAndSetState(0, acquires)) {
            // 上面CAS操作更新成功为1,表示当前线程获取到了锁,因为将当前线程设置为AQS的一个变量中,代表这个线程拿走了锁
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 2. 如果 c 不为 0,即状态不为 0,那么锁已经被拿走了
    // 因为 ReetrantLock 是可重入锁,是可以重复 lock 和 unlock 的,所以这里还要判断一次,获取锁的线程是否为当前请求锁的线程
    else if (current == getExclusiveOwnerThread()) {
        // 如果是,state继续加 1,这里 nextc 的结果就会 > 1,这个判断表示获取到的锁的线程,还可以再获取锁,这里就是说的可重入的意思
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

addWaiter 方法源码:回到aquire方法,还记得调用了一次 addWaiter 方法么

/**
 * 
 * 如果尝试获取同步状态失败的话,则构造同步节点(独占式的Node.EXCLUSIVE),通过addWaiter(Node   
 * node,int args)方法将该节点加入到同步队列的队尾
 */
private Node addWaiter(Node mode) {
    // 用当前线程构建一个 Node 对象,mode 指明当前线程是共享或者独占
    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;
        // 确保节点数据能够安全的被加入队尾 CAS
        // 尝试加锁,如果加锁失败进入 enq 开始自旋
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    // 开始自旋
    enq(node);
    return node;
}

enq 方法源码:通过自旋的方式不断尝试

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;
            }
        }
    }
}

acquireQueued方法:在队列中的线程获取锁的过程

/**
 *
 * acquireQueued方法当前线程在死循环中获取同步状态,而只有前驱节点是头节点才能尝试获取同步状态(锁)
 * 即:p == head && tryAcquire(arg)
 *
 * 1. 头结点是成功获取同步状态(锁)的节点,而头节点的线程释放了同步状态以后,将会唤醒其后继节点,后继
 *    节点的线程被唤醒后要检查自己的前驱节点是否为头结点
 * 2. 维护同步队列的FIFO原则,节点进入同步队列之后,就进入了一个自旋的过程,每个节点都在不断观察自身
 *
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋检查当前节点的前驱节点是否为头节点,才能获取锁
        for (;;) {
            // 获取节点的前驱
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) { // 节点中的线程循环检查自己的前驱是否为头节点
                // 将当前节点设置为头结点,移除之前的头节点
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
          // p != head 或者 p == head但是tryAcquire失败了,那么
          // 应该阻塞当前线程等待前继唤醒。阻塞之前会再重试一次,还需要设置前继的waitStaus为 SIGNAL

            // 线程会阻塞在parkAndCheckInterrupt方法中。
            // parkAndCheckInterrupt返回可能是前继unpark或线程被中断。
            if (shouldParkAfterFailedAcquire(p, node) &&
                 // 说明当前线程是被中断唤醒的。
                // 注意:线程被中断之后会继续走到if处去判断,也就是会忽视中断。
                // 除非碰巧线程中断后acquire成功了,那么根据Java的最佳实践,
                // 需要重新设置线程的中断状态(acquire.selfInterrupt)
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        // 如果有异常
        if (failed)
            // 取消请求,将当前节点从队列中移除
            cancelAcquire(node);
    }
}

shouldParkAfterFailedAcquire 方法的作用是:

  • 确定后继是否需要park
  • 跳过被取消的结点
  • 设置前继的waitStatus为SIGNAL
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)    // 前继结点已经准备好unpark其后继了,所以后继可以安全的park
    /*
     * This node has already set status asking a release
     * to signal it, so it can safely park.
     */
    return true;
if (ws > 0) {   // CANCELLED
    // 跳过被取消的结点。
    do {
        node.prev = pred = pred.prev;
    } while (pred.waitStatus > 0);
    pred.next = node;
} else {  // 0 或 PROPAGATE (CONDITION用在ConditonObject,这里不会是这个值)
     /**
     * waitStatus 等于0(初始化)或PROPAGATE。说明线程还没有park,会先重试 
     * 确定无法acquire到再park。
     */

    // 更新pred结点waitStatus为SIGNAL
    compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;

parkAndCheckInterrupt 就是用 LockSupport 来阻塞当前线程,很简单:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

线程被唤醒只可能是:被unpark,被中断或伪唤醒。被中断会设置interrupted,acquire 方法返回前会 selfInterrupt重置下线程的中断状态,如果是伪唤醒的话会 for 循环 re-check

独占式锁获取同步状态的流程如下:

AQS

3. 独占锁的释放

/**
 *
 * unlock()是解锁函数,它是通过AQS的 release() 函数来实现的
 * 在这里 “1” 的含义和“获取锁的函数 acquire(1) 的含义一样,它是设置 “释放锁的状态” 的参数
 * 由于 “公平锁” 是可重入的,所以对于同一个线程,每释放锁一次,锁的状态-1
 *
 * unlock()在ReentrantLock.java中实现的,源码如下
 */
public void unlock() {
    sync.release(1);
}

releas()会调用 tryRelease 方法尝试释放当前线程持有的锁(同步状态):成功的话唤醒后继线程,并返回 true,否则 false

public final boolean release(int arg) {
    // tryReease由子类实现,通过设置state值来达到同步的效果
    if (tryRelease(arg)) {
        Node h = head;
        // waitStatus为0说明是初始化的空队列
        if (h != null && h.waitStatus != 0)
            // 唤醒后续的结点
            unparkSuccessor(h);
        return true;
    }
    return false;
}
// 在 ReentrantLock 的 Sync 中实现
// 尝试释放当前线程的同步状态
protected final boolean tryRelease(int releases) {
    // c 为释放后的同步状态
    int c = getState() - releases;
    // 判断当前释放锁的线程是否为获取到锁(同步状态)的线程,不是抛出异常(非法监视器状态异常)
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 如果锁(同步状态)已经被当前线程彻底释放,则设置锁的持有者为null,同步状态(锁)变为可获取状态
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

释放锁成功后,找到 AQS 的头节点,并唤醒它即可:

// 唤醒头节点的后继节点
private void unparkSuccessor(Node node) {
    // 获取头节点(线程)的状态
    int ws = node.waitStatus;
    // 如果线程<0,设置当前线程对应所的状态为 0
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    // 从队列尾部开始往前去找最前面的一个 waitStatus 小于 0 的节点
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 唤醒后继节点对应的线程
    if (s != null)
        LockSupport.unpark(s.thread);
}

4. 共享模式获取

acquireShared方法是用来共享模式获取

public final void acquireShared(int arg) {
     //如果没有许可了则入队等待
    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);
                    p.next = null; // help GC
                    if (interrupted)
                        selfInterrupt();
                    failed = false;
                    return;
                }
            }
            // p != head || r < 0
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

核心是这个doAcquireShared方法,跟独占模式的acquireQueued很像,主要区别在setHeadAndPropagate方法中, 这个方法会将 node 设置为 head。如果当前结点 acquire 到了之后发现还有许可可以被获取,则继续释放自己的后继, 后继会将这个操作传递下去。这就是PROPAGATE状态的含义

private void setHeadAndPropagate(Node node, int propagate) {
    Node h = head; // Record old head for check below
    setHead(node);
    /*
     * 尝试唤醒后继的结点:
     * propagate > 0说明许可还有能够继续被线程acquire;
     * 或者 之前的head被设置为PROPAGATE(PROPAGATE可以被转换为SIGNAL)说明需要往后传递;
     * 或者为null,我们还不确定什么情况。
     * 并且 后继结点是共享模式或者为如上为null。
     * 
     * 上面的检查有点保守,在有多个线程竞争获取/释放的时候可能会导致不必要的唤醒。
     * 
     */
    if (propagate > 0 || h == null || h.waitStatus < 0) {
        Node s = node.next;
        // 后继结是共享模式或者s == null(不知道什么情况)
        // 如果后继是独占模式,那么即使剩下的许可大于0也不会继续往后传递唤醒操作
        // 即使后面有结点是共享模式。
        if (s == null || s.isShared())
            // 唤醒后继结点
            doReleaseShared();
    }
} 

private void doReleaseShared() {
    for (;;) {
        Node h = head;
        // 队列不为空且有后继结点
        if (h != null && h != tail) {
            int ws = h.waitStatus;
            // 不管是共享还是独占只有结点状态为SIGNAL才尝试唤醒后继结点
            if (ws == Node.SIGNAL) {
                // 将waitStatus设置为0
                if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                    continue; // loop to recheck cases
                unparkSuccessor(h);// 唤醒后继结点
                // 如果状态为0则更新状态为PROPAGATE,更新失败则重试
            } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                continue; // loop on failed CAS
        }
        // 如果过程中head被修改了则重试。
        if (h == head) // loop if head changed
            break;
    }
} 

5. 共享模式释放

主要逻辑也就是doReleaseShared

public final boolean releaseShared(int arg) {
    if (tryReleaseShared(arg)) {
        doReleaseShared();
        return true;
    }
    return false;
} 

独占和共享模式除了对应的中断版本,还有超时版本,整体代码相差不大。此处给出超时的获取版本,它依据一个变量static final long spinForTimeoutThreshold = 1000L;

private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
    long lastTime = System.nanoTime();
    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;
            }
            if (nanosTimeout <= 0)// 超时
                return false;
            // nanosTimeout > spinForTimeoutThreshold
            // 如果超时时间很短的话,自旋效率会更高。
            if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
            long now = System.nanoTime();
            nanosTimeout -= now - lastTime;
            lastTime = now;
            if (Thread.interrupted())
                throw new InterruptedException();
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
} 

6. NonfairSync 锁的获取

非公平锁和公平锁的唯一区别就是获取锁的方式不同,公平锁是按前后顺序一次获取锁,非公平锁是抢占式的获取锁,那 ReentrantLock 中的非公平锁 NonfairSync 是怎么实现的呢

static final class NonfairSync extends Sync {
    private static final long serialVersionUID = 7316153563782823691L;

    /**
     * Performs lock.  Try immediate barge, backing up to normal
     * acquire on failure.
     */
    final void lock() {
        // 这里是非公平锁多的部分
        // 在 lock 的时候先直接用 CAS 判断 state 变量是否为0(尝试获取锁),成功的话更新成1
        // 表示当前线程获取到了锁,不需要在排队,从而直接抢占的目的
        // 而对于公平锁的 lock 方法是一开始就走 AQS 的双向队列排队获取锁
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }

    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }
}

5. 总结

在获取同步状态的时候,同步器维护一个同步队列,获取失败的线程会被加入到队列中并在队列中自旋;移除队列(或停止自旋)的条件是前驱节点为头结点并且获取到了同步状态。在释放同步状态时,同步器调用tryRelease(int args) 方法释放同步状态,然后唤醒头结点的后继节点。AQS 的实现思路其实并不复杂,用一句话准确的描述的话,其实就是使用标志状态位 status (volatile int state) 和 一个双向队列的入队和出队来实现。AQS维护一个线程何时访问的状态,它只是对状态负责,而这个状态的含义,子类可以自己去定义。

三. AQS 在各同步器内的 Sync 和 state 实现

state 机制:提供 volatile 变量 state,用于同步线程之间的共享状态。通过 CAS 和 volatile 保证其原子性和可见性

/**  
 * 同步状态  
 */    
private volatile int state;    

/**  
 *cas  
 */    
protected final boolean compareAndSetState(int expect, int update) {    
    // See below for intrinsics setup to support this    
    return unsafe.compareAndSwapInt(this, stateOffset, expect, update);    
}   

基于 AQS 构建的 Synchronizer 包括 ReentrantLock,Semaphore,CountDownLatch, ReetrantRead WriteLock,FutureTask 等,这些 Synchronizer 实际上最基本的东西就是原子状态的获取和释放,只是条件不一样而已

ReentrantLock

需要记录当前线程获取原子状态的次数,如果次数为零,那么就说明这个线程放弃了锁(也有可能其他线程占据着锁从而需要等待),如果次数大于1,也就是获得了重进入的效果,而其他线程只能被 park 住,直到这个线程重进入锁次数变成 0 而释放原子状态。以下为 ReetranLock 的 FairSync 的 tryAcquire 实现代码解析

//公平获取锁  
protected final boolean tryAcquire(int acquires) {  
    final Thread current = Thread.currentThread();  
    int c = getState();  
    //如果当前重进入数为0,说明有机会取得锁  
    if (c == 0) {  
        //如果是第一个等待者,并且设置重进入数成功,那么当前线程获得锁  
        if (isFirst(current) &&  
            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;  
}  

Semaphore

则是要记录当前还有多少次许可可以使用,到 0,就需要等待,也就实现并发量的控制,Semaphore 一开始设置许可数为 1,实际上就是一把互斥锁。以下为 Semaphore 的 FairSync 实现

protected int tryAcquireShared(int acquires) {  
    Thread current = Thread.currentThread();  
    for (;;) {  
         Thread first = getFirstQueuedThread();  
         //如果当前等待队列的第一个线程不是当前线程,那么就返回-1表示当前线程需要等待  
         if (first != null && first != current)  
              return -1;  
         //如果当前队列没有等待者,或者当前线程就是等待队列第一个等待者,那么先取得semaphore还有几个许可证,并且减去当前线程需要的许可证得到剩下的值  
         int available = getState();  
         int remaining = available - acquires;  
         //如果remining<0,那么反馈给AQS当前线程需要等待,如果remaining>0,并且设置availble成功设置成剩余数,那么返回剩余值(>0),也就告知AQS当前线程拿到许可,可以继续执行。  
         if (remaining < 0 || compareAndSetState(available, remaining))  
             return remaining;  
 }  
}  

CountDownLatch

闭锁则要保持其状态,在这个状态到达终止态之前,所有线程都会被park住,闭锁可以设定初始值,这个值的含义就是这个闭锁需要被 countDown() 几次,因为每次 CountDown 是 sync.releaseShared(1),而一开始初始值为 10 的话,那么这个闭锁需要被 countDown() 十次,才能够将这个初始值减到 0,从而释放原子状态,让等待的所有线程通过

// await时候执行,只查看当前需要countDown数量减为0了,如果为0,说明可以继续执行,否则需要park住,等
// 待 countDown 次数足够,并且 unpark 所有等待线程  
public int tryAcquireShared(int acquires) {  
     return getState() == 0? 1 : -1;  
}  

// countDown 时候执行,如果当前countDown数量为0,说明没有线程await,直接返回false而不需要唤醒park住
// 线程,如果不为0,得到剩下需要 countDown 的数量并且compareAndSet,最终返回剩下的countDown数量是否
// 为 0,供 AQS 判定是否释放所有await线程
public boolean tryReleaseShared(int releases) {  
    for (;;) {  
         int c = getState();  
         if (c == 0)  
             return false;  
         int nextc = c-1;  
         if (compareAndSetState(c, nextc))  
             return nextc == 0;  
    }  
}  

FutureTask

需要记录任务的执行状态,当调用其实例的 get 方法时,内部类 Sync 会去调用 AQS 的acquireSharedInterruptibly()方法,而这个方法会反向调用 Sync 实现的tryAcquireShared()方法,即让具体实现类决定是否让当前线程继续还是 park,而 FutureTask 的tryAcquireShared()方法所做的唯一事情就是检查状态,如果是 RUNNING 状态那么让当前线程 park。而跑任务的线程会在任务结束时调用 FutureTask 实例的set 方法(与等待线程持相同的实例),设定执行结果,并且通过unpark唤醒正在等待的线程,返回结果

//get时待用,只检查当前任务是否完成或者被Cancel,如果未完成并且没有被cancel,那么告诉AQS当前线程需要进入等待队列并且park住  
protected int tryAcquireShared(int ignore) {  
     return innerIsDone()? 1 : -1;  
}  

//判定任务是否完成或者被Cancel  
boolean innerIsDone() {  
    return ranOrCancelled(getState()) &&    runner == null;  
}  

//get时调用,对于CANCEL与其他异常进行抛错  
V innerGet(long nanosTimeout) throws InterruptedException, ExecutionException, TimeoutException {  
    if (!tryAcquireSharedNanos(0,nanosTimeout))  
        throw new TimeoutException();  
    if (getState() == CANCELLED)  
        throw new CancellationException();  
    if (exception != null)  
        throw new ExecutionException(exception);  
    return result;  
}  

//任务的执行线程执行完毕调用(set(V v))  
void innerSet(V v) {  
     for (;;) {  
        int s = getState();  
        //如果线程任务已经执行完毕,那么直接返回(多线程执行任务?)  
        if (s == RAN)  
            return;  
        //如果被CANCEL了,那么释放等待线程,并且会抛错  
        if (s == CANCELLED) {  
            releaseShared(0);  
            return;  
         }  
        //如果成功设定任务状态为已完成,那么设定结果,unpark等待线程(调用get()方法而阻塞的线程),以及后续清理工作(一般由FutrueTask的子类实现)  
        if (compareAndSetState(s, RAN)) {  
            result = v;  
            releaseShared(0);  
            done();  
            return;  
         }  
    }  
}  

以上4个 AQS 的使用是比较典型,然而有个问题就是这些状态存在哪里呢?并且是可以计数的。从以上4个example,我们可以很快得到答案,AQS 提供给了子类一个 int state 属性。并且暴露给子类 getState() 和setState() 两个方法 (protected)。这样就为上述状态解决了存储问题,RetrantLock 可以将这个 state 用于存储当前线程的重进入次数,Semaphore 可以用这个 state 存储许可数,CountDownLatch 则可以存储需要被countDown 的次数,而 Future 则可以存储当前任务的执行状态 (RUNING,RAN,CANCELL)。其他的Synchronizer 存储他们的一些状态

参考文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值