您的位置:首页 > 产品设计 > UI/UE

AQS(AbstractQueuedSynchronizer)源码解析(共享锁部分)

2018-03-23 16:01 891 查看

阅读须知

JDK版本:1.8

注释规则:

//单行注释做普通注释

/**/多行注释做深入分析

建议配合源码阅读

正文

之前我们分析了AQS的独占锁,AQS同样支持共享锁,jdk并发包中的ReentrantReadWriteLock、Semaphore等,都是基于AQS共享锁实现,推荐读者首先阅读笔者的AQS(AbstractQueuedSynchronizer)源码解析(独占锁部分)这篇文章,里面有对AQS的基本介绍和独占锁部分的源码分析,可以方便大家更好的理解本文。下面我们来分析AQS的共享锁,首先我们来看可以响应中断的共享锁获取方法:

AbstractQueuedSynchronizer:

public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//尝试以共享模式获得锁
if (tryAcquireShared(arg) < 0)
/*采用共享中断模式*/
doAcquireSharedInterruptibly(arg);
}


这里的tryAcquireShared方法由子类实
4000
现共享锁获取逻辑,默认抛出UnsupportedOperationException表示不支持共享模式,我们看到tryAcquireShared方法的返回值是int型,我们来看一下tryAcquireShared方法返回值的含义:

失败为负值;如果在共享模式下获取成功,但后续共享模式没有获取成功,则为零;如果共享模式下的获取成功并且后续的共享模式获取也可能成功,则为正值,在这种情况下,后续等待线程必须检查可用性。(对三种不同返回值的支持使得这个方法可以在独占时才能执行的环境中使用。)一旦成功,就获得了锁。

AbstractQueuedSynchronizer:

private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//为当前线程和给定模式创建并排队节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true; //获取共享锁是否成功标记
try {
for (;;) {
//获取当前节点的前任节点
final Node p = node.predecessor();
//如果前任节点是头节点,则表示当前节点是下一个应该获得锁的节点
if (p == head) {
//尝试获取共享锁
int r = tryAcquireShared(arg);
//根据上面说明的tryAcquireShared方法返回值的含义,这个判断成立代表获取共享锁成功
if (r >= 0) {
/*设置头结点并传播*/
setHeadAndPropagate(node, r);
p.next = null; //帮助GC
failed = false;
return;
}
}
//判断获取锁失败后是否应该阻塞,如果需要阻塞,则在阻塞后判断线程的中断状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
//如果获取锁失败,取消正在进行的获取尝试
cancelAcquire(node);
}
}


这里addWaiter、shouldParkAfterFailedAcquire、parkAndCheckInterrupt、cancelAcquire四个方法我们在AQS独占锁源码分析的文章中都进行过详细说明,这里不再赘述,其中不同的是这里调用addWaiter方法为当前线程和给定模式创建并排队节点时传入的是共享模式。

AbstractQueuedSynchronizer:

private void setHeadAndPropagate(Node node, int propagate) {
Node h = head; //为下面的检查记录旧的头结点
setHead(node); //将当前节点设置为头结点
//propagate>0表示调用方指明了后继节点需要被唤醒
//头结点(旧的或新的)为null或者waitStatus<0,表明后继节点需要配唤醒
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//如果当前节点的后继节点是共享类型后者为null,则进行唤醒
//这里可以理解为除非明确指明不需要唤醒(后继等待节点是独占类型),否则都要唤醒
if (s == null || s.isShared())
/*释放共享锁,并确保转播*/
doReleaseShared();
}
}


AbstractQueuedSynchronizer:

private void doReleaseShared() {
for (;;) {
//这里的头结点已经是上面新设置的头结点了,也就是刚刚获取到共享锁的节点
Node h = head;
if (h != null && h != tail) {
int ws = h.waitStatus;
//如果节点的等待状态是SIGNAL,证明后面的节点需要被唤醒
if (ws == Node.SIGNAL) {
//CAS操作防止并发重复唤醒
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; //如果CAS失败循环重新检查
//唤醒后继节点
unparkSuccessor(h);
}
//如果后继节点暂时不需要唤醒,则把当前节点状态设置为PROPAGATE确保传播
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; //如果CAS失败循环重新检查
}
//如果头结点发生变化,需要循环保证唤醒动作的传播
//如果头节点未发生变化,则结束当前循环
if (h == head)
break;
}
}


方法中唤醒后继节点的操作我们在AQS独占锁源码分析的文章中进行过详细分析,这个方法是共享锁中的核心唤醒方法,主要做的事情就是唤醒下一个线程或者设置传播状态。后继线程被唤醒后,会尝试获取共享锁,如果成功获取,则又会调用setHeadAndPropagate将唤醒传播下去。这个方法的作用是保障在acquire和release存在竞争的情况下,保证队列中处于等待状态的节点能够有办法被唤醒。

下面我们来看共享锁释放的流程:

AbstractQueuedSynchronizer:

public final boolean releaseShared(int arg) {
//尝试释放共享锁
if (tryReleaseShared(arg)) {
//释放共享锁,并确保转播,刚刚分析过
doReleaseShared();
return true;
}
return false;
}


这里的tryReleaseShared由子类覆盖,通过尝试设置state变量来释放共享锁,默认抛出UnsupportedOperationException,表示不支持共享模式。

上文我们介绍了响应中断的共享锁获取方法,下面我们来看忽略中断的共享锁获取方法:

AbstractQueuedSynchronizer:

public final void acquireShared(int arg) {
//尝试以共享模式获得锁
if (tryAcquireShared(arg) < 0)
/*采用共享不中断模式*/
doAcquireShared(arg);
}


AbstractQueuedSynchronizer:

private void doAcquireShared(int arg) {
//为当前线程和给定模式创建并排队节点
final Node node = addWaiter(Node.SHARED);
boolean failed = true; //获取共享锁是否成功标记
try {
boolean interrupted = false; //中断标记,初始化为false
for (;;) {
//获取当前节点的前任节点
final Node p = node.predecessor();
//如果前任节点是头节点,则表示当前节点是下一个应该获得锁的节点
if (p == head) {
//尝试获取共享锁
int r = tryAcquireShared(arg);
//根据上面说明的tryAcquireShared方法返回值的含义,这个判断成立代表获取共享锁成功
if (r >= 0) {
//设置头结点并传播
setHeadAndPropagate(node, r);
p.next = null; //帮助GC
if (interrupted)
//如果中断标记为true,则中断当前线程
selfInterrupt();
failed = false;
return;
}
}
//判断获取锁失败后是否应该阻塞,如果需要阻塞,则在阻塞后判断线程的中断状态
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true; //这是中断标记为true
}
} finally {
if (failed)
//如果获取锁失败,取消正在进行的获取尝试
cancelAcquire(node);
}
}


整体逻辑和响应中断的共享锁获取方法很相似,不同的就是在阻塞后判断线程的中断状态后的处理。

到这里,AQS共享锁部分的源码解析就完成了。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: