juc- 从源码角度分析 AbstractQueuedSynchronizer 的实现
AbstractQueuedSynchronizer
翻译过来是 AbstractQueuedSynchronizer , 也是我们常说的 AQS 他是很多同步器的基础框架,比如 ReentrantLock、CountDownLatch 和 Semaphore 等都是基于 AQS 实现的。除此之外,我们还可以基于 AQS,定制出我们所需要的同步器。同步器的设计是基于模板方法模式的,也就是说,使用者需要继承同步器并重写指定的 方法,随后将同步器组合在自定义同步组件的实现中,并调用同步器提供的模板方法,而这些 模板方法将会调用使用者重写的方法。
原理概述
- state 表示同步器的状态
- head 和 tail 组成同步器 的 CLH(虚拟的双向队列)双向队列
- ConditionObject 实现了 Condition (也称为条件队列或条件变量) 是每个对象具有了等待集 , 如果锁取代了同步方法和语句的使用,则条件取代了对象监视器方法的使用。
- unsafe Java中的Unsafe类为我们提供了类似C++手动管理内存的能力。在AQS 执行CAS操作
重写同步器
重写同步器指定的方法时,需要使用同步器提供的如下3个方法来访问或修改同步状态
- getState():获取当前同步状态。
- setState(int newState):设置当前同步状态。
- compareAndSetState(int expect,int update):使用CAS设置当前状态,该方法能够保证状态 设置的原子性
同步器可重写的方法
方法名称 | 描 述 |
---|---|
protected boolean tryAcquire(int arg) | 独占式获取同步状态,实现该方法需要查询当前状态并频段同步状态是否符合预期,然后再进行CAS设置同步状态 |
protected boolean tryRelease(int arg) | 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 |
protected int tryAcquireShared | 共享式获取同步状态,返回大于等于 0 的值表示获取成功,反之获取失败 |
protected boolean tryReleaseShared(int arg) | 共享式释放同步状态 |
protected boolean isHeldExclusively() | 但钱同步器是否在独占模式下被线程占用,一般该方法表示是否被当前线程锁独占 |
public final void acquire(int arg) | 独占式获取同步状态,如果单线线程同步状态获取成功,则有该方法返回,否则进入同步队列等待 |
public final void acquireInterruptibly(int arg) | 与acquire(int arg) 相同,但是该方法显影中断,当前线程为获取到同步状态二进入同步队列中,如果当前线程被中断,则该方法会判处InterruptedException并返回 |
public final boolean tryAcquireNanos(int arg, long nanosTimeout) | 在 acquireInterruptibly 基础上增加了超市限制,如果当前线程在超市时间内没有获取到同步状态,那么会返回false,如果获取到了返回 true |
public final void acquireShared(int arg) | 共享式获取同步状态,如果当前线程为获取到同步状态,将会进入同步队列等待,与独占是获取的主要区别式在同一时刻可以又多个线程获取到同步状态 |
public final void acquireSharedInterruptibly(int arg) | 与 acquireShared(int arg 相同,该方法响应中断 |
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout) | 在acquireSharedInterruptibly(int arg)基础上增加了超时限制 |
public final boolean release(int arg) | 独占式 释放同步状态,该方法在释放同步状态之后,将同步队列中第一个节点包含的线程唤醒 |
public final boolean releaseShared(int arg) | 共享式释放同步状态 |
public final Collection getQueuedThreads() | 获取等待在同步队列上的线程集合 |
同步器提供的方法可以分为三大类
- 独占式获取与释放同步状态、
- 共享式获取与释放
- 同步状态和查询同步队列中的等待线程情况
同步状态 state
AQS 中的 state 是 volatile 修饰的 保证了字段可见性 使用 compareAndSetState 对 state进行操作 保证了原子性
AQS 获取、释放资源是否成功都是由state决定的
- ReentrantLock的state用来表示是否有锁资源
- ReentrantReadWriteLock的state高16位代表读锁状态,低16位代表写锁状态
- Semaphore的state用来表示可用信号的个数
- CountDownLatch的state用来表示计数器的值
同步队列
node节点
同步器依赖内部的同步队列(一个FIFO双向队列)来完成同步状态的管理,当前线程获取同步状态失败时,同步器会将当前线程以及等待状态等信息构造成为一个节点(Node)并将其 加入同步队列,同时会阻塞当前线程,当同步状态释放时,会把首节点中的线程唤醒,使其再 次尝试获取同步状态.
同步队列中的节点(Node)用来保存获取同步状态失败的线程引用、等待状态以及前驱和后继节点,节点的属性类型与名称以及描述
static final class Node {
// 用于指示节点正在共享模式下等待的标记
static final Node SHARED = new Node();
// 用于指示节点正在独占模式下等待的标记
static final Node EXCLUSIVE = null;
/** waitStatus 表示当前节点以取消调度(超时或者被中断) */
static final int CANCELLED = 1;
/** waitStatus 表示后继节点处于等待状态 */
static final int SIGNAL = -1;
/** waitStatus 表示该节点在等待队列上,调用条件变量的 signal 方法 该节点会从等待队列转移到同步队列 */
static final int CONDITION = -2;
/** waitStatus 表示下一次共享式同步状态获取将会无条件被传播下去*/
static final int PROPAGATE = -3;
volatile int waitStatus;
// 前驱节点
volatile Node prev;
// 后继节点
volatile Node next;
// 获取同步状态的线程
volatile Thread thread;
// 等待队列的后继节点
Node nextWaiter;
}
同步队列结构
当一个线程成功地获取了同步状态(或者锁),其他线程将无法获取到同步状态,转 而被构造成为节点并加入到同步队列中,而这个加入队列的过程必须要保证线程安全,因此同步器提供了一个基于CAS的设置尾节点的方法:compareAndSetTail(Node expect,Node update),它需要传递当前线程“认为”的尾节点和当前节点,只有设置成功后,当前节点才正式 与之前的尾节点建立关联。
独占式同步状态获取
首先调用自定义同步器实现的tryAcquire(int arg)方法,该方法 保证线程安全的获取同步状态,如果同步状态获取失败,则构造同步节点(独占式 Node.EXCLUSIVE,同一时刻只能有一个线程成功获取同步状态)
并通过addWaiter(Node node) 方法将该节点加入到同步队列的尾部,
最后调用acquireQueued(Node node,int arg)方法,使得该 节点以“死循环”的方式获取同步状态。
如果获取不到则阻塞节点中的线程,而被阻塞线程的 唤醒主要依靠前驱节点的出队或阻塞线程被中断来实现。
public final void acquire(int arg) {
// 尝试获取同步状态
if (!tryAcquire(arg) &&
// 构建尾节点 以死循环的方式获取同步状态
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
// 创建 Node节点
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 判断尾节点 是不是空
if (pred != null) {
// 如果尾节点不等于null,把当前节点的前驱节点指向尾节点
node.prev = pred;
// 替换尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果添加失败或队列不存在,执行end函数
enq(node);
return node;
}
在enq(final Node node)方法中,同步器通过“死循环”来保证节点的正确添加,在“死循环”中只有通过CAS将节点设置成为尾节点之后,当前线程才能从该方法返回,否则,当前线 程不断地尝试设置。可以看出,enq(final Node node)方法将并发添加节点的请求通过CAS变 得“串行化”了。
private Node enq(final Node node) {
for (;;) { //循环
//获取尾节点
Node t = tail;
if (t == null) {
//如果尾节点为空,创建哨兵节点,通过cas把头节点指向哨兵节点
if (compareAndSetHead(new Node()))
//cas成功,尾节点指向哨兵节点
tail = head;
} else {
//当前节点的前驱节点设指向之前尾节点
node.prev = t;
//cas设置把尾节点指向当前节点
if (compareAndSetTail(t, node)) {
//cas成功,之前尾节点的下个节点指向当前节点
t.next = node;
return t;
}
}
}
}
节点进入同步队列之后,就进入了一个自旋的过程,每个节点(或者说每个线程)都在自省地观察,当条件满足,获取到了同步状态,就可以从这个自旋过程中退出,否则依旧留在这 个自旋过程中,并会阻塞节点的线程
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;
}
// 判断是否需要挂起当前线程 主要是 判断是否为 Node.SIGNAL 状态, 如果是执行parkAndCheckInterrupt 调用线程的 park 阻塞线程
if (shouldParkAfterFailedAcquire(p, node) &&
// 调用线程的 park 阻塞线程 线程在这里阻塞住 可能被唤醒 或者被打断 返回true 代表线程被打断
parkAndCheckInterrupt())
// 标记被打断
interrupted = true;
}
} finally {
//如果出现异常或者出现中断,就会执行finally的取消线程的请求操作,核心代码是node.waitStatus = Node.CANCELLED;将线程的状态改为CANCELLED。
if (failed)
cancelAcquire(node);
}
}
队列中各个节点的状态
独占式同步状态获取流程,也就是acquire(int arg)方法调用流程
前驱节点为头节点且能够获取同步状态的判断条件和线程进入等待状态是获取同步状态的自旋过程。当同步状态获取成功之后,当前线程从acquire(int arg)方法返回,如果 对于锁这种并发组件而言,代表着当前线程获取了锁。
独占式同步状态释放
当前线程获取同步状态并执行了相应逻辑之后,就需要释放同步状态,使得后续节点能够继续获取同步状态。通过调用同步器的release(int arg)方法可以释放同步状态,该方法在释 放了同步状态之后,会唤醒其后继节点(进而使后继节点重新尝试获取同步状态)
public final boolean release(int arg) {
// 释放同步状态
if (tryRelease(arg)) {
Node h = head;
// 头节点不为空 且 waitStatus 不等于零
if (h != null && h.waitStatus != 0)
// 调用后继节点的 unpark
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 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)
//unpark 唤醒线程
LockSupport.unpark(s.thread);
}
独占式超时获取同步状态
通过调用同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超时获取同步状态,即在指定的时间段内获取同步状态,如果获取到同步状态则返回true,否则,返回false。
超时获取同步状态过程可以被视作响应中断获取同步状态过程的“增强版”,doAcquireNanos(int arg,long nanosTimeout)方法在支持响应中断的基础上,增加了超时获取的 特性
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();
if (nanosTimeout <= 0L)
return false;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
// park 当前线程 时间为剩余时间
LockSupport.parkNanos(this, nanosTimeout);
// 判断线程的打断状态 并清空打断状态
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
共享式同步状态获取与释放
共享式同步状态获取 与 独占锁的获取基本一直 差别就是共享式支持多个线程共享访问
在共享式获取的自旋过程中,成功获取到同步状态并退出自旋的条件就是 tryAcquireShared(int arg)方法返回值大于等于0。
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;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
与独占式一样,共享式获取也需要释放同步状态,通过调用releaseShared(int arg)方法可以
释放同步状态
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
// 唤醒后记节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
if (h == head) // loop if head changed
break;
}
}