一. 同步器
多线程并发的执行,之间通过某种 共享 状态来同步,只有当状态满足某个条件,才能触发线程执行。
而这个共同的语义可以称之为同步器,可以认为几乎 juc ( java.util.concurrent ) 下面所有的锁机制都可以基于同步器定制来实现的。而 juc 的思想是将这些场景抽象出来的语义通过同意的同步框架来支持。
juc 里所有的这些锁机制都是基于 AQS ( AbstractQueuedSynchronizer ) 框架上构建的。具体介绍可以参考 Doug Lea 的论文 The java.util.concurrent Synchronizer Framework
上图中,Lock 的实现类其实都是构建在 AQS 上,之所以没有在图上表示出来,是因为每个 Lock 实现类都持有自己内部类 Sync 的实例,而这个 Sync 是继承自 AQS,之所以要实现不同的 Sync 是和每种 Lock 用途相关。
二. AQS 如何构建同步器
AQS 的核心思想是基于volatile int state
这样的 volatile 变量,配合 Unsafe 工具对其原子性的操作来实现对当前锁状态进行修改。同步器内部依赖一个 FIFO 的双向队列来完成资源获取线程的排队工作。
同步器的基本功能:一个同步器至少需要包含两个功能
获取同步状态
如果允许,则获取锁;如果不允许就阻塞线程,知道同步状态允许获取
释放同步状态
修改同步状态,并且唤醒等待线程
而在作者的论文中,AQS 同步机制同时考虑了如下需求:
- 独占锁和共享锁两种机制
- 线程阻塞后,如果需要取消,需要支持中断
- 线程阻塞后,如果有超时要求,应该支持超时后中断的机制
1. AQS 同步器的结构
同步器拥有首节点 (head) 和尾节点 (tall),同步队列的基本结构如下:
同步队列设置尾节点(未获取到锁的线程加入同步队列)
同步器 AQS 中包含两个节点类型的引用:一个指向头节点的引用 (head),一个指向尾节点的引用 (tall),当一个线程成功的获取到锁(同步状态),其他线程无法获取到锁,而是被构造成节点 (Node:包含当前线程、等待状态) 加入到同步队列中等待获取到锁的线程释放锁。
这个加入队列的过程,必须要保证线程安全。否则如果多线程的环境下,可能造成添加到队列等待的节点顺序错误,或者数量不对,因此同步器提供了 CAS 原子的设置尾节点的方法(保证一个未获取到同步线程加入到同步队列后,下一个未获取的线程才能够加入),如下图所示:
同步队列设置头节点(原头节点释放锁,唤醒后继节点)
同步队列遵守 FIFO,头节点是获取锁(同步状态)成功的节点,头节点在释放同步状态的时候,会唤醒后继节点,而后继节点将会在获取锁(同步状态)成功的时候将自己设置为头节点。设置头节点是由获取锁(同步状态)成功的线程来完成的。
由于只有一个线程能够获取同步状态,因此设置头节点的方法不需要 CAS 保证。只需要将头节点设置成为设置成为原头节点的后继节点,并断开原头节点的 next 引用,如下图所示:
2. 独占锁的获取
调用同步器的acquire(int arg)
方法可以获取锁(同步状态),该方法对中断不敏感,即线程获取同步状态失败后进入同步队列,后续对线程进行中断操作时,线程不会从同步队列中移除。
获取锁的流程:
- 当前线程实现通过
tryAcquire()
方法尝试获取锁,获取成功的话直接返回;如果尝试失败的话,进入等待队列排队等待,可以保证线程安全 (CAS) 的获取同步状态 - 如果尝试获取锁失败的话,构造同步节点(独占式的
Node.EXCLUSIVE
),通过addWaiter(Node node,int args)
方法,将节点加入到同步队列的队列尾部 - 最后调用
acquireQueued(final Node node, int args)
方法,使该节点以自旋的方式获取同步状态,如果获取不到,则阻塞节点中的线程。而且只有前驱节点是头节点的时候才能尝试获取锁(同步状态)p == head && tryAcquire(arg)
原因:
- 头节点是成功获取同步状态的节点,而头节点的线程释放锁以后,将唤醒后继节点,后继节点线程被唤醒后要检查自己的前驱节点是否为头节点
- 维护同步队列的 FIFO 原则,节点进入同步队列以后,就进入一个自旋的过程,每个节点都在不断观察自己
独占锁的获取源码分析
阻塞地获取锁: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
独占式锁获取同步状态的流程如下:
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 存储他们的一些状态
参考文章: