基础 - AQS - 源码解读(二)
简介
前面一篇分析了ReentrantLock的方法和内部类,我们了解到它里面并没有定义state属性、exclusiveOwnerThread属性、acquire方法等,这些都是使用AQS的。本篇着重讲AbstractQueuedSynchronizer的独占锁。一定要看完第一篇,不然很难看明白AbstractQueuedSynchronizer
AbstractQueuedSynchronizer 抽象类
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
AbstractQueuedSynchronizer 继承 AbstractOwnableSynchronizer抽象类
AbstractOwnableSynchronizer 抽象类
public abstract class AbstractOwnableSynchronizer
implements java.io.Serializable
AbstractOwnableSynchronizer 抽象类作用就是确定锁归属
AbstractOwnableSynchronizer 属性
private transient Thread exclusiveOwnerThread;
AbstractOwnableSynchronizer 构造函数
protected AbstractOwnableSynchronizer() { }
AbstractOwnableSynchronizer 方法
// 设置锁拥有者
protected final void setExclusiveOwnerThread(Thread thread) {
exclusiveOwnerThread = thread;
}
// 获取锁拥有者
protected final Thread getExclusiveOwnerThread() {
return exclusiveOwnerThread;
}
AbstractQueuedSynchronizer 重要内部类 Node
static final class Node
Node 属性
// 共享模式
static final Node SHARED = new Node();
// 独占模式
static final Node EXCLUSIVE = null;
// 状态:取消
static final int CANCELLED = 1;
// 状态:
static final int SIGNAL = -1;
// 状态:
static final int CONDITION = -2;
// 状态:
static final int PROPAGATE = -3;
// 节点状态
volatile int waitStatus;
// 上一个节点
volatile Node prev;
// 下一个节点
volatile Node next;
// 节点对应的线程
volatile Thread thread;
// 下一个等待着
Node nextWaiter;
Node 构造函数
// 无参构造函数
Node() {
}
// 使用线程和下一个节点构建
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
// 使用线程和状态构建
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
Node 方法
// 是否是共享模式
final boolean isShared() {
return nextWaiter == SHARED;
}
// 获取上一个节点,若上一个节点为空,抛出异常
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
AbstractQueuedSynchronizer 属性
// 头节点
private transient volatile Node head;
// 尾节点
private transient volatile Node tail;
// 状态
private volatile int state;
// 内存操作不安全类
private static final Unsafe unsafe = Unsafe.getUnsafe();
// 下面属性都是偏移量
private static final long stateOffset;
private static final long headOffset;
private static final long tailOffset;
private static final long waitStatusOffset;
private static final long nextOffset;
AbstractQueuedSynchronizer 静态代码块
static {
try {
stateOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("state"));
headOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("head"));
tailOffset = unsafe.objectFieldOffset
(AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
waitStatusOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("waitStatus"));
nextOffset = unsafe.objectFieldOffset
(Node.class.getDeclaredField("next"));
} catch (Exception ex) { throw new Error(ex); }
}
AbstractQueuedSynchronizer 构造函数
protected AbstractQueuedSynchronizer() { }
AbstractQueuedSynchronizer 基础方法
// 获取锁状态
protected final int getState() {
return state;
}
// 设置锁状态
protected final void setState(int newState) {
state = newState;
}
// CAS设置锁状态
protected final boolean compareAndSetState(int expect, int update) {
// 设置锁状态
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
// 设置头元素
private final boolean compareAndSetHead(Node update) {
return unsafe.compareAndSwapObject(this, headOffset, null, update);
}
// 设置尾元素
private final boolean compareAndSetTail(Node expect, Node update) {
return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}
// 设置等待状态
private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
}
// 设置下一个元素
private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
}
AbstractQueuedSynchronizer 获取独占锁
// 以独占模式获取锁
public final void acquire(int arg) {
// 获取锁或者等待
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// 只有中断状态,并且获取锁成功才会进来
selfInterrupt();
}
ReentrantLock.lock()方法中,FairSync公平模式下直接调用acquire(1),NonfairSync非公平模式下先CAS获取一次锁,获取失败也会调用acquire(1)。
acquire方法第一步会调用tryAcquire方法
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
在AbstractQueuedSynchronizer抽象类中tryAcquire直接调用是会抛异常的,必须要在子类中覆盖此方法,FairSync、NonfairSync均有tryAcquire方法的实现。仔细看源码的就会发现,非公平模式实际上会使用CAS获取两次锁,第一次lock里面,第二次在这儿。
当tryAcquire获取锁失败时,调用addWaiter方法构建节点
private Node addWaiter(Node mode) {
// 构建节点
Node node = new Node(Thread.currentThread(), mode);
// 尾插法
Node pred = tail;
if (pred != null) {
node.prev = pred;
// 设置新的尾节点
if (compareAndSetTail(pred, node)) {
// CAS设置成功说明tail指向node
pred.next = node;
return node;
}
}
// 能到这儿,说明tail为空或者CAS修改tail失败
// 调用enq入队(补全双向链表节点关系)
enq(node);
// 返回node
return node;
}
尾节点为空或者CAS修改新尾节点失败时调用enq入队,这有一个问题,如果CAS设置node为尾节点失败,这时node只设置了上节点即node的prev属性,而上级节点的next是不关联node的,也就是说目前这个节点还没入队,但是从这个节点可以向前推到head节点,但是从头节点向后推一定推不到node节点。再来看CAS修改成功pred.next = node 还未执行,这时node已经变成尾节点,node的pred前面已经设置过了,但是上级节点的next还未指向node,这时从head往后找,只能找到原尾节点(oldTail.next还未设置),所以在并发中,要遍历整个链表正确的姿势是从尾节点遍历漏掉节点的概率最低(只设置了prev属性并且CAS失败,这种还未完全入队的就没办法了)。
private Node enq(final Node node) {
// 自旋
for (;;) {
// 尾节点
Node t = tail;
// 判断尾节点是否为空
if (t == null) {
// 初始化双向链表
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 把node追加在tail后
// addWaiter中已经设置过prev的也会被覆盖
node.prev = t;
// 修改node为新的尾节点
if (compareAndSetTail(t, node)) {
t.next = node;
// 这里返回的是原tail,不是node,虽然没用
return t;
}
}
}
}
在enq方法中先看链表有没有初始化,如果没有初始化就会初始化,同样设置新尾节点时,先设置了prev属性,再使用CAS修改node为新尾节点,跟addWaiter方法入队逻辑一样,只不过这里是自旋一定会入队,一样会有从head向后遍历漏元素的问题。
问题:这里还有一个问题,这里为什么要new一个Node为头节点,为什么不把node设置为tail = head=node?这个问题在解锁的时候回答。
回到acquire方法中,acquire中先获取一次锁,获取失败时使用addWaiter构建node对象,并调用acquireQueued方法
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);
// 原上级节点的next置空
p.next = null;
// 线程获取锁成功标志为取消
failed = false;
// 返回中断状态
return interrupted;
}
// 先判断是否可以park,可以就直接park了,否则继续自旋
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
// 正常情况下是不会到这的
// 只有failed = false前面异常才会进来
cancelAcquire(node);
}
}
acquireQueued方法中,有且只有p == head && tryAcquire(arg)时才能修改头节点,删除原头节点,并且退出,就算线程被中断,该方法也会忽略中断,但是会返回中断状态。acquireQueued中调用shouldParkAfterFailedAcquire方法,判断当前节点是否可以park
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 上级节点状态
int ws = pred.waitStatus;
// 上级节点状态为SIGNAL -1时可以park
if (ws == Node.SIGNAL)
// 返回可以park
return true;
// 上一个节点状态为CANCELLED 1时,重新找上级节点
if (ws > 0) {
// 往上找一个状态不是CANCELLED的节点置为上级节点
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
// 设置新上级节点的next
pred.next = node;
} else {
// 状态为int默认值 0、CONDITION -2 、PROPAGATE -3
// 修改上级节点状态为SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 返回不能park
return false;
}
shouldParkAfterFailedAcquire方法还算好理解,主要就是找上级节点,和重置上级节点状态。重置失败也没关系acquireQueued中自旋还会走进来。如果shouldParkAfterFailedAcquire方法返回可以park,那么调用parkAndCheckInterrupt方法进行park
private final boolean parkAndCheckInterrupt() {
// 使当前线程park(这一句执行完线程就阻塞了)
LockSupport.park(this);
// 清除中断状态,并返回原中断状态
return Thread.interrupted();
}
parkAndCheckInterrupt方法中调用LockSupport.park,线程就在这等待了,后面代码都不能执行了。但是LockSupport.park是可以响应中断的,当被唤醒或者响应中断后,使用Thread.interrupted()先清楚中断,然后返回原中断状态(在acquire方法中会使用selfInterrupt()继续传递中断状态)
acquireQueued 的finally中,异常是会调用cancelAcquire
private void cancelAcquire(Node node) {
// 判断node是否为空,为空直接忽略
if (node == null)
return;
// 设置node线程为空
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;
// 判断是否为尾,修改尾节点为新找到的上级节点
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
int ws;
// 上级节点不是头节点,并且状态为SIGNAL
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 上级节点的下级执行node的下级
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 取消node节点,主要是unpark后续节点(后面说)
unparkSuccessor(node);
}
node.next = node; // 下级节点指向自己
}
}
AbstractQueuedSynchronizer 释放独占锁
public final boolean release(int arg) {
// tryRelease在Sync中有实现
if (tryRelease(arg)) {
// 获取头节点
Node h = head;
// 非公平模式下只有一个线程时,
// 不需要unparkSuccessor
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
unpark后续节点
private void unparkSuccessor(Node node) {
// 获取node状态
int ws = node.waitStatus;
// 状态小于0
if (ws < 0)
// 修改状态为0
compareAndSetWaitStatus(node, ws, 0);
// 获取下级节点
Node s = node.next;
// 下级节点为空或者状态为取消
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾节点往前找离node最近且不为取消的节点,
// 为什么不能从head找,前面已经解释过了
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// 下级节点不为空
if (s != null)
// 终止下级节点等待
LockSupport.unpark(s.thread);
}
细心的可能能发现unparkSuccessor唤醒的是头节点的下一个节点,第一次看的时候这里有一个问题困扰了很久,非公平模式下头节点岂不是会一直在等待?后来想明白了,这种情况不可能出现。
首先在初始化队列时使用的是空Node,A、B两个线程并发,A插队,B入队,此时队列里两个元素空Node和B。A解锁时B被唤醒,B获得锁修改自己为头节点,删除空Node;
如果head释放锁后,A插队,B入队。这时需要注意,释放锁是不会删除head节点,只有在头节点的下一个节点获得锁,才会删除head,这时head跟上面空Node是一个意义了。
问题回答
enq中为什么要new一个Node为头节点,为什么不把node设置为tail = head=node?
如果直接把node设置为头尾,就跟下面unparkSuccessor逻辑冲突了,除非unparkSuccessor中改为唤醒头节点,并且acquireQueued改为获取锁就出队