文章目录
AQS - 同步状态的获取与释放(共享式)
-
同步状态的获取与释放2大类,共享式和独占式,主要针对的就是第一篇文章中所描述的几个独占式模板方法。
表格所示如下: -
子类重写的流程方法所示如下:
方法作用 | 共享式 |
---|---|
获取同步状态 | tryAcquireShared(int arg) |
释放同步状态 | tryReleaseShared(int arg) |
判断是否被当前线程所独占 | isHeldExclusively() |
- AQS中模板方法所示如下:
方法作用 | 共享式 |
---|---|
获取同步状态 | acquireShared(int arg) |
获取同步状态(可响应中断) | acquireSharedInterruptibly(int arg) |
获取同步状态(可超时) | tryAcquireSharedNanos(int arg, long nanosTimeout) |
释放同步状态 | releaseShared(int arg) |
- 在同步状态的获取与释放的部分,AQS采用了模板方法的设计模式,子类继承ASQ然后重写对应的流程方法,AQS中的模板方法会调用对应重写的流程方法,线程队列的处理等细节已经
在AQS中实现好了。AQS面对的是锁和其他同步组件的实现者,继承AQS可以很方便的实现自定义的锁。
一、共享式获取同步状态
- 下面我们分析独占式获取共享状态的方法中,有不支持中断的,支持中断和支持超时参数的,这些方法的都在AQS中实现了基本的方法流程骨架,将不能确定的逻辑部分再流程方法中
由子类实现,因此交给流程方法由子类实现。
1.1 不支持中断的acquireShared(int arg)
1.1.1 acquireShared(int arg)
/**
* 共享模式获取同步状态,忽略中断(不响应中断)
*/
public final void acquireShared(int arg) {
//1.调用流程方法,共享式获取同步状态,获取成功就返回了,获取失败就调用doAcquireShared
if (tryAcquireShared(arg) < 0)
doAcquireShared(arg);
}
1.1.2 doAcquireShared
/**
* Acquires in shared uninterruptible mode.
*
* @param arg the acquire argument
* 共享式获取同步状态的自旋方法,忽略中断,
* 如果尝试获取失败,就会在这里面自旋
* 和独占式类似,会忽略中断,内部通过一个布尔变量保存是否被中断过,但是这个布尔值并没有返回(貌似也什么用)
* 在独占式中是会返回这个布尔值的
*/
private void doAcquireShared(int arg) {
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (; ; ) {
//1.获取前驱节点
final Node p = node.predecessor();
//2.如果前驱是head,并且获取成功(tryAcquireShared返回值大于0表示获取成功)
//那么就设置相关变量,并返回
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
//3.如果前驱不是头结点,那么自己就没有机会,就维护节点内部状态,并且park挂起进程
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//4.取消
if (failed)
cancelAcquire(node);
}
}
- shouldParkAfterFailedAcquire和parkAndCheckInterrupt、cancelAcquire在上一篇独占式的文章中已经分析,2种模式下这几个方法是一样的,就不过多
分析了。
1.2 支持中断的acquireSharedInterruptibly(int arg)
1.2.1 acquireSharedInterruptibly(int arg)
- acquireShared(int arg)是不支持中断的,acquireSharedInterruptibly支持中断,其实方法主体2者差不多,区别在doAcquireSharedInterruptibly里面收
到中断信号后抛出异常,但是acquireShared里面的主体的doAcquireShared不支持中断(只是记录了一下)。
/**
* 共享式获取同步状态的自旋方法,可以响应中断,
*/
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
//1.中断了就抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//2.也是先尝试获取,获取成功了就返回,获取失败了就调用doAcquireSharedInterruptibly自旋获取,并且在自旋
// 中支持对中断信号的处理
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
}
1.2.2 doAcquireSharedInterruptibly(int arg)
- 共享式支持中断的同步状态获取主要流程在doAcquireSharedInterruptibly中实现,其实看过之前的独占式我们再看共享式其实差不多,支持中断无非是在for
循环里面自旋的时候,处理中断信号,主体逻辑和doAcquireShared几乎一模一样,对比上一篇博客,其实独占式和共享式,处理流程方法不同之外,模板方法都
是差不多的。
/**
* Acquires in shared interruptible mode.
*
* @param arg the acquire argument
* 共享式支持中断的同步状态获取主要流程实现
* 和不支持中断的主体实现方法doAcquireShared逻辑几乎一模一样,只是在接收到
* 中断信号时抛出异常,而doAcquireShared只是记录一下
*/
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);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//如果中断了不是记录变量,而是抛出异常
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
1.3 支持超时的tryAcquireSharedNanos(int arg, long nanosTimeout)
1.3.1 tryAcquireSharedNanos(int arg, long nanosTimeout)
- 支持超时的共享式同步状态获取方法,主体方法和之前的doAcquireShared、doAcquireSharedInterruptibly都差不多。
/**
* 支持超时的共享式同步状态获取方法,
*/
public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//如果tryAcquireShared(arg) >= 0说明成功直接返回,如果失败就调用doAcquireSharedNanos
//方法,方法逻辑和不响应中断的。响应中断的,三者都差不多
return tryAcquireShared(arg) >= 0 || doAcquireSharedNanos(arg, nanosTimeout);
}
1.3.2 doAcquireSharedNanos(int arg, long nanosTimeout)
- 主体逻辑和doAcquireShared、doAcquireSharedInterruptibly都差不多,增加了对超时逻辑的处理,参考上一篇独占式的分析中的doAcquireNanos。
private boolean doAcquireSharedNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
final long deadline = System.nanoTime() + nanosTimeout;
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
//主体方法和之前的doAcquireShared、doAcquireSharedInterruptibly都差不多。
//和独占式的支持超时的逻辑也差不多
//和文章03-Java多线程、线程等待通知机制的超时范式逻辑相仿
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();
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);
}
}
二、共享式释放同步状态
2.1.1 releaseShared(int arg)
- 共享式释放是一个并发的操作,因此需要考虑线程安全。
/**
* 共享式同步状态释放方法,返回值取决于tryReleaseShared流程方法的返回值
*/
public final boolean releaseShared(int arg) {
//1.尝试释放,释放失败就进入doReleaseShared
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
2.1.2 doReleaseShared()
- 共享式释放同步状态,自旋保证线程安全。
private void doReleaseShared() {
//1.自旋直到释放成功
for (; ; ) {
Node h = head;
//2.队列不为空且有后继结点
if (h != null && h != tail) {
int ws = h.waitStatus;
//3.不管是共享还是独占只有结点状态为SIGNAL才尝试唤醒后继结点
if (ws == Node.SIGNAL) {
//将waitStatus设置为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒后继结点
unparkSuccessor(h);
} else if ( // 如果状态为0则更新状态为PROPAGATE,更新失败则重试
ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
// 如果过程中head被修改了则重试,没有的话就退出
if (h == head) // loop if head changed
break;
}
}
三、总结
- 共享式在不状态的获取和释放,我们看到主题流程其实和独占式差不多,区别核心在于流程方法,流程方法是在子类实现的。
- 对比几种不同的获取方法,不支持中断的,支持中断的,支持超时的,其实主体逻辑都差不多,只是在自旋的过程中增加对中断的响应
和超时的判断处理。