目录
AQS实际上就是CLH的一个变种,为了更好的理解AQS的原理我们先了解一下CLH的原理。
CLH锁
CLH锁就是一种基于单项队列排队的自旋锁,由于是Craig、Landin、Hagersten三人一起发明的,因此成为CLH锁
AQS结构
AQS核心成员
通过上图我们可以看到AQS有哪些成员,我们来分别看看
属性 | 说明 |
head | 等待队列的头部,延迟初始化。 除初始化外,只能通过 setHead 方法进行修改。 注意:如果head存在,它的waitStatus一定不会是CANCELED |
tail | 等待队列的尾节点,延迟初始化。只能通过添加等待节点来修改尾节点 |
state | state是AQS核心参数,表示锁状态,0-表示没有被占用 大于0则表示持有锁的线程数量,这里为什么说是大于0不是1,是因为锁可以重入,每次重入state都会自增 |
内部类
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 的值,表示线程在一个条件上等待 */
static final int CONDITION = -2;
/**
* waitStatus 的值,表示下一个acquireShared应该无条件传播
*/
static final int PROPAGATE = -3;
volatile int waitStatus;
//前驱节点
volatile Node prev;
//后继节点
volatile Node next;
// 当前线程
volatile Thread thread;
AQS阻塞链表
通过分析Node,AQS的链表结构就很清晰了,注意head节点是持有锁的,所说的阻塞队列是从第二个节点算起的,通过后面代码可以发现如果节点的直接前驱是可以直接
尝试获取锁的。
ReentrantLock源码分析
ReentrantLock 是一种可重入互斥锁,其基本行为和语义与使用同步方法和语句访问的隐式监视器锁(synchronized
)相同,但是会比synchronized
更加灵活。 ReentrantLock 由上次成功锁定但尚未解锁的线程拥有。 当锁不被另一个线程拥有时,调用锁的线程将返回,成功获取锁。 如果当前线程已经拥有锁,该方法将立即返回。
FairSync 公平锁
公平锁就是如果获取不到锁,就参与排队等待获取,不会存在插队情况,简化的流程图如下:
源码分析
lock
公平锁实现就是调用AQS中的acquire方法,具体源码如下:
static final class FairSync extends Sync {
// 争锁
final void lock() {
acquire(1);
}
}
acquire是在AQS中实现是首先尝试获取锁,如果获取锁失败就加入到队列中。
其代码如下:
public final void acquire(int arg) {
//尝试获取锁失败后,这个时候需要把当前线程挂起,放到阻塞队列中;
//如果在获取锁的等待过程中发生中断,则执行selfInterrupt
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire又是抽象方法,延迟到子类FairSync中实现的,首先判断当前锁有没有被占用,如果么有被占用,则当前线程尝试占用锁;如果已经被占用,则看看是不是被当前线程占用,如果是则允许锁重入。具体代码实现如下:
protected final boolean tryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取当前节点的状态
int c = getState();
//state == 0 此时此刻没有线程持有锁
if (c == 0) {
//如果没有前驱节点在排队,则CAS获取锁;如果获得了锁则将独占锁的持有者设置为当前线程
if (!hasQueuedPredecessors() &&
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;
}
//如果没有获取到锁则返回false
return false;
}
如果tryAcquire(1)没有获取到锁,则继续执行 acquireQueued(addWaiter(Node.EXCLUSIVE), arg)
private Node addWaiter(Node mode) {
//对当前线程创建一个新节点
Node node = new Node(Thread.currentThread(), mode);
//把当前node加到链表的最后面去,也就是进到阻塞队列的最后
Node pred = tail;
//如果tail节点不为null
if (pred != null) {
//将当前节点的前驱节点设置为目前的队尾节点
node.prev = pred;
//CAS将当前节点设置为队尾,如果设置成功则将之前的队尾的后继节点设置为当前节点,并返回
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//pred==null(队列是空的) 或者 CAS失败(有线程在竞争入队),则执行enq(node)
//采用自旋的方式入队
enq(node);
return node;
}
// 采用自旋的方式入队
// 之前说过,到这个方法只有两种可能:阻塞队列为空,或者有线程竞争入队,
// 自旋在这边的语义是:CAS设置tail过程中,竞争一次竞争不到,就多次竞争,总会排到的
private Node enq(final Node node) {
//自旋
for (;;) {
//队尾节点
Node t = tail;
//如果队尾为空
if (t == null) {
//初始化head节点,原来head和tail初始化的时候都是null,
//此时初始化阻塞队列,CAS设置一个新节点作为头节点
if (compareAndSetHead(new Node()))
//这个时候有了head,但是tail还是null,需要把tail指向head
tail = head;
} else {
//队尾节点不为空,这个套在无限循环里,反正就是将当前线程排到队尾,
//有线程竞争的话排不上重复排
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
//下面这个方法,参数node,经过addWaiter(Node.EXCLUSIVE),此时已经进入阻塞队列
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//是否中断标记
boolean interrupted = false;
//自旋
for (;;) {
//获取前驱节点
final Node p = node.predecessor();
// p == head 说明当前节点虽然进到了阻塞队列,但是是阻塞队列的第一个,因为它的前驱是head
// 注意,阻塞队列不包含head节点,head一般指的是占有锁的线程,head后面的才称为阻塞队列;
//如果前驱节点是头节点则可以执行tryAcquire,因为初始化队列的时候头节点并不占有锁,
//tryAcquire方法执行成功表示已经抢到锁;则将该节点设置为头节点,后继节点为null
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);
}
}
/*
* 节点获取锁失败后,检查,更新节点waitstatus
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//获取前驱节点的waitStatus的值
int ws = pred.waitStatus;
//前驱节点的 waitStatus == -1 ,说明前驱节点状态正常,当前线程需要挂起,直接可以返回true
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
/*
* 前驱节点 waitStatus大于0 ,大于0 说明前驱节点取消了排队
* 进入阻塞队列排队的线程会被挂起,而唤醒的操作是由前驱节点完成的。
* 所以下面这块代码说的是将当前节点的prev指向waitStatus<=0的节点,跳过已经取消的节点
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
*前驱节点的waitStatus不等于-1和1;都没有看到有设置waitStatus的,
*所以每个新的node入队时,waitStatu都是
* 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1)
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
shouldParkAfterFailedAcquire(Node pred, Node node)
这个方法结束根据返回值我们简单分析下:
如果返回true, 说明前驱节点的waitStatus==-1,是正常情况,那么当前线程需要被挂起,等待以后被唤醒
如果返回false, 说明当前不需要被挂起,为什么呢? 因为如果返回false,则表示pred已经是头节点了;也就是当前节点是head的直接后继,再次循环的时候就可以直接尝试获取锁了,没有必要在被挂起了。
如果返回true则执行 parkAndCheckInterrupt()
private final boolean parkAndCheckInterrupt() {
//当前线程挂起
LockSupport.park(this);
//中断检查
return Thread.interrupted();
}
至此,线程已经被挂起了,下面在看一下解锁相关代码
unlock
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//尝试释放锁,如果释放成功
if (tryRelease(arg)) {
Node h = head;
//头节点不为null且waitStatus不是0则唤醒后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//当前状态减去releases;
int c = getState() - releases;
//判断当前线程是否是持有锁的线程,该方法可以放在第一句执行
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
// 是否完全释放锁
boolean free = false;
//如果c=0表示锁已经完全释放了
if (c == 0) {
free = true;
//将持有锁的线程设置为null
setExclusiveOwnerThread(null);
}
//将c的值更新到state
setState(c);
return free;
}
/**
*如果存在后继节点则唤醒后继节点
*/
private void unparkSuccessor(Node node) {
//获取当前节点的值
int ws = node.waitStatus;
//如果当前节点的status为负数则CAS修改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//下面的代码就是唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)
//获取当前节点的后继节点
Node s = node.next;
//如果后继节点为null或后继节点已经被取消
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);
}
此时线程被唤醒,则阻塞的线程则从这个 parkAndCheckInterrupt() 方法的中断检查开始执行
如果线程没有中断,则会在 acquireQueued中自旋进行锁竞争
在看看指定超时时间的场景
/**
* 在指定的时间内线程没有中断则获取没有被其他线程持有的锁;如果竞争到锁则立即返回true,
*
**/
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
/**
* 尝试在独占模式下获取锁,线程中断活着超时情况下终止。
**/
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
//线程中断检查
if (Thread.interrupted())
throw new InterruptedException();
//首先尝试获取锁,如果失败,则一直尝试获取锁为止,或超时以及线程中断
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
//如果指定的超时时间小于或等于0则直接返回false
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();
//如果直接前驱是head节点则尝试获取锁,如果可以获取锁,则将该节点设置为头节点,同时将该节点的后继节点设置为null
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
//计算剩余等待时间
nanosTimeout = deadline - System.nanoTime();
//如果剩余可等待时间小于或等于0则返回false
if (nanosTimeout <= 0L)
return false;
//还可以等待一段时间,则检查并更新线程状态,如果需要被挂起且剩余等待时间大于自旋优先的阈值,则将该线程最多挂起nanosTimeout 纳秒
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
//最多挂起 nanosTimeout 纳秒
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
//如果超时或线程中断抛出InterruptedException异常时failed为true
if (failed)
cancelAcquire(node);
}
}
/**
* 取消正在获取锁的尝试
*/
private void cancelAcquire(Node node) {
// 如果节点不存在则直接返回
if (node == null)
return;
//将node节点持有的线程设置为null
node.thread = null;
// 跳过被取消的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
// predNext is the apparent node to unsplice. CASes below will
// fail if not, in which case, we lost race vs another cancel
// or signal, so no further action is necessary.
//前驱节点的后继节点
Node predNext = pred.next;
//将当前节点的等待状态设置为取消状态
node.waitStatus = Node.CANCELLED;
// 如果当前节点就是队尾,将该节点删除
if (node == tail && compareAndSetTail(node, pred)) {
compareAndSetNext(pred, predNext, null);
} else {
//如果自己不是队尾节点
int ws;
//前驱节点也不是头节点且 等待状态为-1或cas更新为-1同时前驱节点线程不为null
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//当前节点的后继节点
Node next = node.next;
//后继节点不为null且没有持有锁,则将前驱节点的直接后继执行后继节点,进而将该节点从链表中删除
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
//如果前驱节点不满足上述条件则唤醒前驱节点
unparkSuccessor(node);
}
//将该节点的后继执行自己,断开链接
node.next = node; // help GC
}
}
通过上面的源码可以发现虽然线程中断了但是该节点依然会参与锁竞争,下面我们在看一下可中断的加锁实现
/**
* 竞争独占锁,线程中断则终止
*/
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//首先检查线程是否中断,如果中断则抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取锁,如果获取锁失败则执行doAcquireInterruptibly
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
//将当前节点添加到阻塞队列
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
//自旋
for (;;) {
//获取当前节点的直接前驱节点
final Node p = node.predecessor();
//如果直接前驱是head节点则尝试获取锁,如果可以竞争到锁则将当前节点设置为头节点
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
//判断需要挂起,同时线程已经中断则抛出中断异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
// 如果通过 InterruptedException 异常出去,那么 failed 就是 true 了
if (failed)
cancelAcquire(node);
}
}
上面主要讲述了公平锁,如果是非公平锁又是怎样的呢?
NonfairSync
static final class NonfairSync extends Sync {
private static final long serialVersionUID = 7316153563782823691L;
final void lock() {
//直接进行CAS尝试进行获取锁,这个比公平锁多出来的操作
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
final boolean nonfairTryAcquire(int acquires) {
//获取当前线程
final Thread current = Thread.currentThread();
//获取state值
int c = getState();
//c为0,表示没有线程持有锁,则进行cas操作获取锁
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
} //判断当前线程是否持有锁,如果是就重入
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
公平锁和非公平锁只有两处不同:
- 非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,如果这个时候恰巧锁没有被占用,那么直接就获取到锁返回了。
- 非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在 tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程处于等待状态,如果有则需要排队等待。
公平锁和非公平锁就这两点区别,如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
Condition
上面主要走读了独占锁的实现逻辑,下面主要走读Condition相关的实现逻辑
public class BoundedBuffer {
final Lock lock = new ReentrantLock();
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
public void put(Object obj) throws InterruptedException {
lock.lock();
try {
//如果当前数量已经达到上限则在notFull条件上等待
while (count == items.length) {
notFull.await();
}
items[putptr] = obj;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
notEmpty.await();
}
Object obj = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal();
return obj;
} finally {
lock.unlock();
}
}
}
public Condition newCondition() {
return sync.newCondition();
}
final ConditionObject newCondition() {
return new ConditionObject();
}
下面看下ConditionObject结构
// 条件队列的第一个节点 private transient Node firstWaiter; // 条件队列的最后一个节点 private transient Node lastWaiter;首先看在条件上阻塞操作
public final void await() throws InterruptedException {
//如果线程已经中断,则抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
//向条件等待队列中添加一个新的Waiter节点
Node node = addConditionWaiter();
//完全释放该节点的锁,只有完全释放才可以避免重入问题,并将释放前的值返回;后续再次获取锁的时候需要用到
int savedState = fullyRelease(node);
int interruptMode = 0;
//如果当前节点已经在阻塞队列中或者在等待过程中中断过,如果该节点不在阻塞队列中则线程在该处挂起
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
//线程唤醒后会进行中断检查
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//线程唤醒后将该节点加入到阻塞队列 如果在唤醒前就发生了中断则interruptMode 设置为REINTERRUPT
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//如果后继节点不为null 则进行清理取消的节点
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//interruptMode不为0则会根据interruptMode的值决定是抛出异常还是执行中断或则什么都不做
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
将节点添加到等待队列
/**
* 向等待队列中添加一个新的waiter,并返回这个新的等待节点
*/
private Node addConditionWaiter() {
//等待队列的最后一个节点
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
//如果最后一个节点不为null但是状态又不是condition,则需要清理等待队列
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//新建一个等待节点
Node node = new Node(Thread.currentThread(), Node.CONDITION);
//如果最后一个节点为null,则将该节点设置为最后节点,否则将该节点添加到最后节点之后并将该节点设置为最后节点
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//等待队列是一个单向链表,遍历链表将已经取消等待的节点清除出去
private void unlinkCancelledWaiters() {
//获取第一个等待节点
Node t = firstWaiter;
Node trail = null;
//如果第一个节点不为null
while (t != null) {
//获取下一个节点
Node next = t.nextWaiter;
//如果第一个节点的等待状态不是condition
if (t.waitStatus != Node.CONDITION) {
//将下一个节点的直接后继设置为null
t.nextWaiter = null;
//如果trail为null则将第二个节点提升为第一个节点,否则trail的直接后继节点指向next,将当前节点从链表中删除
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
//遍历到队尾,将trial指向到节点设置为最后一个等待节点
if (next == null)
lastWaiter = trail;
}
else//如果第一个等待节点是condition状态,则trail指向第一个节点,t指向下一个节点
trail = t;
t = next;
}
}
final int fullyRelease(Node node) {
boolean failed = true;
try {
//获取当前同同步状态
int savedState = getState();
//这里使用了当前的 state 作为 release 的参数,也就是完全释放掉锁,将 state 置为 0
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
final boolean isOnSyncQueue(Node node) {
//如果 waitStatus 还是 Node.CONDITION,也就是 -2,那肯定就是还在条件队列中
//如果 node 的前驱 prev 指向还是 null,说明肯定没有在 阻塞队列
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
//如果存在后继节点则一定在阻塞队列中
//node.prev() != null 来推断出 node 在阻塞队列?不能是因为在addWaiter方法中会将node.prev指向为队尾,但是此时
//节点上位加入到阻塞队列中
if (node.next != null)
return true;
// 这个方法从阻塞队列的队尾开始从后往前遍历找,如果找到相等的,说明在阻塞队列,否则就是不在阻塞队列
return findNodeFromTail(node);
}
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
//如果t==node表示该节点在阻塞队列中
if (t == node)
return true;
//如果全部遍历完还是没有找到相同的节点则返回false
if (t == null)
return false;
t = t.prev;
}
}
如果当前等待节点不在阻塞队列中,则需要执行LockSupport.park(this)将线程挂起;那么什么时候唤醒呢?后面会详细说;假设此时线程被唤醒,唤醒后需要进行中断检查,如果中断检查当返回值不是0,即发生了中断则从while循环中跳出。
/**
* 中断检查,如果在唤醒前中断则返回 THOW_IE,如果在唤醒后中断则返回
* REINTERRUPT;如果没有中断则返回0
*/
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
从while循环中跳出后,执行 if (acquireQueued(node, savedState) && interruptMode != THROW_IE),acquireQueued是做什么的呢?上文已经有介绍过了,主要是用来锁竞争和线程挂起。如果竞争到了锁且未发生中断,则await阻塞结束,执行后续业务代码。但是我们知道acquireQueued要求当前Node是在阻塞队列的,那么什么时候加入阻塞队列的呢?下面我们看一下signal方法
/**
* 如果等待队列存在线程,将等待时间最长的线程从等待队列转移到锁的阻塞队列中
*
*/
public final void signal() {
//没有独占锁抛出异常
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//condition等待队列的第一个节点
Node first = firstWaiter;
//如果第一个节点不为null
if (first != null)
doSignal(first);
}
private void doSignal(Node first) {
do {
//当前节点的下一个节点设置为第一个节点,如果这个节点为null则将最后一个节点设置为null,当前节点的下一个节点也设置为null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
//循环推出的条件是:将condition等待队列的第一个节点转移到阻塞队列成功或等待队列中元素为空
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
*CAS 设置节点waitStatus的值为0,如果设置失败,即当前节点的状态不是CONDITION,标示该节点已经被取消了
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//将该节点自旋加入阻塞队列的队尾,并返回该节点的前驱节点
Node p = enq(node);
//获取前驱节点的状态
int ws = p.waitStatus;
// ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁,直接唤醒 node 对应的线程
//如果 ws <= 0, 那么 compareAndSetWaitStatus 将会被调用,节点入队后,需要把前驱节点的状态设为 Node.SIGNAL(-1),如果设置失败则唤起线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
如果发生了中断会怎么处理内?
/**
* 如果interruptMode是 THROW_IE 则抛出中断异常
* 如果interruptMode是 REINTERUPT 则再次执行中断
* 其他情况什么都不做
*/
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
selfInterrupt();
}
至此已经将排它锁相关逻辑介绍完成,下面介绍共享锁相关逻辑。
CountDownLatch
CountDownLatch结构
首先看一下await()方法
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
/**
* 在共享模式下获取锁,线程中断则终止
*/
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//首先进行线程中断检查,如果中断则抛出中断异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取锁,如果获取锁失败则执行doAcquireSharedInterruptibly()方法,
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
//只有state为0的时候才返回1,否则就返回-1
protected int tryAcquireShared(int acquires) {
return (getState() == 0) ? 1 : -1;
}
/**
* 在共享可中断模式下获取锁
*/
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//为当前线程创建一个共享模式节点并加入到阻塞队列中
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
//自旋
for (;;) {
//获取当前新建节点的直接前驱节点
final Node p = node.predecessor();
//如果直接前驱节点为head节点
if (p == head) {
//尝试获取锁
int r = tryAcquireShared(arg);
//获取到锁
if (r >= 0) {
//将当前节点设置为头节点和传播
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//如果当前节点的直接前驱不是head或没有获取到锁,则需要判断当前节点是否需要挂起,如果需要则通过parkAndCheckInterrput()方法挂起
//如果发生中断则抛出异常
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
/**
*
* 设置队列头,并检查后继节点是否可以在共享模式下等待,如果不需要等待则可以唤醒后继节点
*/
private void setHeadAndPropagate(Node node, int propagate) {
//当前头节点
Node h = head;
//将当前节点设置为新的头节点
setHead(node);
//如果propagate大于0,通过上面代码可以知道propagate的值为1,如果下一个节点不为null且只共享模式则执行doReleaseShared()方法
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
if (s == null || s.isShared())
doReleaseShared();
}
}
/**
* 共享模式下释放锁的操作,唤醒后继节点同时保证传播功能
*/
private void doReleaseShared() {
for (;;) {
//头节点
Node h = head;
//如果头节点不为null同时也不能与尾节点(初始化的时候头节点等于尾节点)
if (h != null && h != tail) {
//头节点的等待状态
int ws = h.waitStatus;
//如果头节点的状态时SINGLE(-1),则将头节点设置为0,失败重试,成功则唤醒头节点
if (ws == Node.SIGNAL) {
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue;
unparkSuccessor(h);
}//如果头节点是新加入的节点,则将其状态设置为-3
//为什么不可以是-1呢?这是因为这个方法执行结束后需要执行chouldParkAfterFail,-1在此会被挂起
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue;
}
//如果头节点没有变化则跳出循环,反之则继续循环
if (h == head)
break;
}
}
public void countDown() {
sync.releaseShared(1);
}
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
protected boolean tryReleaseShared(int releases) {
// 对state值进行自减,如果结果为0则返回true
for (;;) {
int c = getState();
if (c == 0)
return false;
int nextc = c-1;
if (compareAndSetState(c, nextc))
return nextc == 0;
}
}
public boolean await(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//如果没有获取锁则执行doAcquireSharedNanos方法
return tryAcquireShared(arg) >= 0 ||
doAcquireSharedNanos(arg, nanosTimeout);
}
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
//计算出deadLine
final long deadline = System.nanoTime() + nanosTimeout;
//为当前线程创建一个共享模式节点并添加到阻塞队列
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return true;
}
}
//计算剩下等待时间
nanosTimeout = deadline - System.nanoTime();
//如果剩下等待时间小于或等于0则表示超时了,返回false
if (nanosTimeout <= 0L)
return false;
//判断是否需要挂起,如果剩余等待时间小于阈值则不挂起而是通过自旋处理;
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}