综述
AbstractQueuedSynchronizer 是java 多线程阻塞库和同步器实现的基础类。以下简称为AQS。AQS 底层数据结构是一个FIFO的双端队列。实现依赖单一原子状态(int state)state,state的更新操作必须是原子的,子类必须Overload protected方法改变状态,通过状态值可以实现获取锁和释放锁的定义。此类支持排它锁和共享锁。在互斥锁下,其他的线程不能成功的获取锁。在共享下,多个线程可以获取锁但不一定成功。共享锁和互斥锁的区别是,在共享锁下,当一个线程获取锁成功后,其他的等待线程也可以尝试获取锁。共享锁和互斥锁在实现上使用同一个FIFO队列。
为了使用这个类,必须实现以下几个方法:这些方法的默认实现抛出UnsupportedOperationException
1. tryAcquire
2. tryRelease
3. tryAcquireShared
4. tryReleaseShared
5. isHeldExclusively
AQS实现可以支持两种锁模式:共享锁和互斥锁
总结:当一个线程加入 CHL队列时,如果不是头结点需要判断该节点是否需要挂起;在释放锁时,需要唤醒该节点的后继节点
Acquire和Release实现模式
以下是互斥模式的实现:
Acquire:
while (!tryAcquire(arg)) {
如果当前线程不在队列就压入队列;
可能阻塞当前线程;
}
Release:
if (tryRelease(arg))
释放队列头部的节点;
共享模式是类似的,但涉及到唤醒它的后继节点。
等待队列节点类
等待队列节点是一个双端队列。需要等待的线程加入到队列的尾部,线程释放从队列的头部开始。线程的出队和入队的操作必须是原子性的。队列结构图如下:
队列中的pre主要为了处理被取消的线程。如果一个线程被取消,它的后继者必须重新连接到一个不是被取消的节点。
使用next节点实现阻塞机制,每个node节点都包含自己的线程Id,
Header节点是一个哨兵节点,实际不包含线程。
线程的挂起与调度通过工具类LockSupport实现,分别是park()和uppark方法。
static final class Node {
/* 表示一个线程在共享锁下正在等待/
static final Node SHARED = new Node();
/* 表示一个线程在排它锁下正在等待 /
static final Node EXCLUSIVE = null;
/** 如果waitStatus==1表示此线程被取消*/
static final int CANCELLED = 1;
/**如果waitStatus==-1表示后继线程需要被唤醒 */
static final int SIGNAL = -1;
/** 如果waitStatus==-2表示此线程在等待条件 */
static final int CONDITION = -2;
/**
* 如果waitStatus==-3表示下一个acquireShared应该被无条件的传播
*/
static final int PROPAGATE = -3;
//waitStatus的默认值表示为0,
volatile int waitStatus;
####node源码类
static final class Node {
/** 表示一个线程在共享锁下正在等待*/
static final Node SHARED = new Node();
/** 表示一个线程在排它锁下正在等待 */
static final Node EXCLUSIVE = null;
/** 如果waitStatus==1表示此线程被取消*/
static final int CANCELLED = 1;
/**如果waitStatus==-1表示后继线程需要被唤醒 */
static final int SIGNAL = -1;
/** 如果waitStatus==-2表示此线程在等待条件 */
static final int CONDITION = -2;
/**
* 如果waitStatus==-3表示下一个acquireShared应该被无条件的传播
*/
static final int PROPAGATE = -3;
//waitStatus的默认值表示为0,
volatile int waitStatus;
prev 节点说明
/**
* 当前节点或者线程的前节点。当一个前继节点被取消时,我
* 们可以总是发现一个没有被取消的节点作为前继节点,因为
* 一个head节点绝不会被取消,当一个节点变成head节点,
* 只有当此节点成功的获取锁。一个被取消的线程在获取锁时
* 绝不能成功。一个线程只能取消自己。
*/
volatile Node prev;
//队列头节点,挂起线程的唤醒在队列头部进行
private transient volatile Node head;
//队列尾节点,没有获取锁的线程加入到队列尾部
private transient volatile Node tail;
//同步状态
private volatile int state;
UnSafe compareAndSetXXX(expect,update),如果当前值和期望值相等,则更新成update值。
protected final boolean compareAndSetState(int expect, int update) {
//stateOffset 字段state内存地址偏移量,为了获取当前
//state的值
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
循环入队实现
队列入队时把节点加入到队列尾部,因为有别的线程可能也在进行入队操作,因此循环操作实现队列入队。
如果:tail == null,创建一个node,并让tail和heand同时指向此node。
如果 tail !=null,设置node.pre=tail,tail = node
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;
}
}
}
}
线程加入等待队列实现:
如果队列尾节点不为空,直接尝试加入到尾部,如果失败,则调用enq(node)方法,循环入队。
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
//首先尝试直接把此节点加入到队列尾部,如果失败,则调用
//enq(node)循环,直到加入成功。
Node pred = tail;
if (pred != null) {
node.prev = pred;
//原子的更新尾节点
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//调用入队操作
enq(node);
return node;
}
唤醒后继节点,如果存在。
找到下一个节点,如果为null后者被取消,则从尾节点反向遍历找到第一个需要被唤醒的节点
private void unparkSuccessor(Node node) {
/*
* 由于需要唤醒后继节点的线程释放了锁。
* waitStatus=SINGAL
* 原子的设置waitStatus=0
*/
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* 找到下一个节点,如果为null后者被取消,则从尾节点反
* 向遍历找到第一个需要被唤醒的节点。
*/
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);
}
释放共享锁时唤醒后继节点
/**
* 释放共享锁。如果当前节点waitStatus = signal,则唤醒后
* 继节点。如果waitStatus=0,则设置waitStatus=progagate
*
*/
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;
//唤醒后继节点
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; //fail 继续
}
if (h == head) //保证head没改变
break;
}
}
获取共享锁代码实现
private void doAcquireShared(int arg) {
//添加到等待队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
/*
*获取此节点的前继节点。
*如果前继节点==head节点。则获取锁
*
*/
final Node p = node.predecessor();
if (p == head) {
//尝试获取锁,如果成功则返回大于0
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);
}
}