JAVA并发编程-AQS底层实现原理及应用(一)
acquireQueued方法中的Finally代码:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
...
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
...
failed = false;
...
}
...
} finally {
if (failed)
//等待期间出现异常或其他问题
cancelAcquire(node);
}
}
通过cancelAcquire方法,将Node的状态标记为CANCELLED,节点被取消
private void cancelAcquire(Node node) {
// 如果当前节点为null,直接忽略。
if (node == null)
return;
//1. 设置该节点不关联任何线程,也就是虚节点
node.thread = null;
//2. 往前跳过被取消的节点,找到一个有效节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//3. 拿到了上一个节点之前的next
Node predNext = pred.next;
//4. 当前节点状态设置为1,代表节点取消
node.waitStatus = Node.CANCELLED;
// 脱离AQS队列的操作
// 当前Node是尾结点,将tail从当前节点替换为上一个节点
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
// 到这,上面的操作CAS操作失败
int ws = pred.waitStatus;
// 不是head的后继节点
if (pred != head &&
// 如果当前节点不是head的后继节点,1:判断当前节点前驱节点的是否为SIGNAL,2:如果不是,则把前驱节点设置为SINGAL看是否成功
// 如果1和2中有一个为true,再判断当前节点的线程是否为null
// 如果上述条件都满足,把当前节点的前驱节点的后继指针指向当前节点的后继节点
(ws == Node.SIGNAL || (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL)))
&& pred.thread != null) {
Node next = node.next;
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 如果当前节点是head的后继节点,或者上述条件不满足,那就唤醒当前节点的后继节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
获取当前节点的前驱节点,如果前驱节点是cancel,就一直往前找,直到waitStatus<=0,将找到的Pred节点和当前Node关联,将当前Node设置为CANCELLED。
根据节点位置分三种情况处理。
对解锁的基本流程进行分析。由于ReentrantLock在解锁的时候,并不区分公平锁和非公平锁,所以我们直接看解锁的源码:
// java.util.concurrent.locks.ReentrantLock
public void unlock() {
sync.release(1);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer
public final boolean release(int arg) {
//返回true代表锁资源释放完
if (tryRelease(arg)) {
Node h = head;
//头节点不是null,并且头节点状态不是0,可以唤醒后继阻塞节点
//1.h == null Head还没初始化。
//2.h != null && waitStatus == 0 表明后继节点对应的线程仍在运行中,不需要唤醒。
//3.h != null && waitStatus < 0 表明后继节点可能被阻塞了,需要唤醒。
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
在ReentrantLock里面的公平锁和非公平锁的父类Sync定义了可重入锁的释放锁机制。
//java.util.concurrent.locks.ReentrantLock.Sync
protected final boolean tryRelease(int releases) {
// 减少可重入次数
int c = getState() - releases;
// 当前线程不是持有锁的线程,抛出异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果持有线程全部释放,将当前ExclusiveOwnerThread属性设置为null,并更新state
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
再看一下unparkSuccessor方法
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//我们都是根据前一节点的waitStatus判断是否唤醒当前节点,所以设置当前节点状态位0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//获取后继节点
Node s = node.next;
//如果下个节点是null或者下个节点被cancelled,就找到队列最开始的非cancelled的节点
if (s == null || s.waitStatus > 0) {
s = null;
//从后往前找到离node最近的且不是null并且waitStatus<=0的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果当前节点的s节点不为空,而且状态<=0,就把s节点unpark
if (s != null)
LockSupport.unpark(s.thread);
}
q1:为什么从后往前找第一个非cancel的的节点,为不是从前往后,
a1:
原因:1>我们可以看到// java.util.concurrent.locks.AbstractQueuedSynchronizer#addWaiter方法,节点入队并不是原子操作,也就是说,node.prev = pred; compareAndSetTail(pred, node) 这两个地方可以看作Tail入队的原子操作,但是此时pred.next = node;还没执行,如果这个时候执行了unparkSuccessor方法,就没办法从前往后找了,所以需要从后往前找。
原因:2>还有一点原因,在产生CANCELLED状态节点的时候,先断开的是Next指针,Prev指针并未断开,因此也是必须要从后往前遍历才能够遍历完全部的Node
唤醒后,会执行return Thread.interrupted();,这个函数返回的是当前执行线程的中断状态,并清除状态。
// java.util.concurrent.locks.AbstractQueuedSynchronizer
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
再回到acquireQueued代码,当parkAndCheckInterrupt返回True或者False的时候,interrupted的值不同,但都会执行下次循环。如果这个时候获取锁成功,就会把当前interrupted返回。
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;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
如果返回true,则会执行,线程打上中断标记位
// java.util.concurrent.locks.AbstractQueuedSynchronizer
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
但为什么获取了锁以后还要中断线程呢?