【源码解析系列】JAVA AQS队列源码解析第二章
目录
前言
之前我们讲了【源码解析系列】JAVA AQS队列源码解析第一章,传送门:https://blog.csdn.net/weixin_38357164/article/details/157291871?fromshare=blogdetail&sharetype=blogdetail&sharerId=157291871&sharerefer=PC&sharesource=weixin_38357164&sharefrom=from_link相信你通过这篇文已经对AQS队列有了一定的理解。上一章我们重点讲了关于AQS队列部分线程进入队列和出队列的过程,以及线程获取锁(不超时不中断)的和释放锁的流程。今天我们一起开始探究AQS队列第二章内容,也就是线程支持超时获取锁和可中断获取锁部分的源码解析。
实际应用
需要支持可超时获取锁
对于线程支持可超时获取锁来说是必要的,因为对于高并发的场景,如果线程获取锁不支持超时那么线程就会一直等待获取锁这是致命的。
对于服务端的业务来说。请求耗时可谓分秒必争,网络io,数据库io、服务端分布式链路深度和业务复杂度都决定了一个接口的相应时间。如果线程不支持超时获取锁就是可怕的。因为一个线程阻塞会引起后面所有线程阻塞。所有的请求都会等待获取锁超时一直到网络的链接超时限制。而且等待队列占用的线程不会释放,也同样会消耗线程池资源。所以线程获取锁等待必须支持超时,获取不到锁就可以快速失败业务。当然也可以选择其他的补偿机制
-
快速失败:立即返回错误或默认值
-
重试机制:指数退避重试
-
降级策略:返回缓存数据或简化逻辑
-
补偿机制:记录日志,异步补偿
需要支持可中断获取锁
线程支持可中断获取锁是为了让多线程程序能够更安全、更灵活地处理线程的停止和取消操作。通过中断,我们可以实现优雅的线程终止,让线程有机会清理资源。同时提供一种线程间协作的机制。
在编写多线程程序时,我们应该充分利用中断机制,而不是使用已废弃的stop()、suspend()等方法。同时,要注意正确处理中断异常,通常有两种做法
-
重新设置中断状态:
Thread.currentThread().interrupt(); -
将中断异常传递给调用者。
AQS可超时获取锁
tryAcquireNanos方法
说到AQS实现超时获取锁的方法是tryAcquireNanos,他的调用者(具体锁实现)如下图所示

下面我们来看一下tryAcquireNanos方法如何实现获取锁超时的。
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
//如果线程已经被中断则结束
if (Thread.interrupted())
throw new InterruptedException();
//tryAcquire先尝试获取锁,只有在tryAcquire失败后才执行doAcquireNanos超时等待
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
tryAcquire方法
tryAcquire是需要锁实现需要重写的方法,这里先不关注,只知道如果该方法返回true就表示获取锁成功了。
doAcquireNanos方法
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
//计算超时时间deadline
final long deadline = System.nanoTime() + nanosTimeout;
//将线程加入等待队列后拿到当前线程节点
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
//拿到当前节点的前驱节点
final Node p = node.predecessor();
//如果前驱节点是头结点,尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
//获取失败继续自旋,并判断是否超时
nanosTimeout = deadline - System.nanoTime();
//如果超时则结束自旋返回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);
}
}
cancelAcquire(node)方法
如果结束方法的时候没有获取到锁,将节点从同步队列中移除,因为该节点不再需要等待获取锁(原因是中断或超时)
private void cancelAcquire(Node node) {
//为空返回
if (node == null)
return;
//释放线程和节点的绑定关系
node.thread = null;
//跳过取消的前驱节点
Node pred = node.prev;
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
//取过滤后的pred的后继节点,也就是当前节点
Node predNext = pred.next;
//将节点状态设为 CANCELLED(值为1)这一步后,其他线程可以看到这个节点已取消
node.waitStatus = Node.CANCELLED;
//当前节点是尾节点,并且可以CAS将tail设置成当前节点的前驱节点
if (node == tail && compareAndSetTail(node, pred)) {
//则将前驱节点的下一个节点设为null,这样当前node节点就断开与链表的链接。双向绑定关系解除。
compareAndSetNext(pred, predNext, null);
} else {
//节点不是尾节点或cas失败
int ws;
//pred 不是头节点
//pred 状态是 SIGNAL 或可设置为 SIGNAL
//pred 关联的线程不为空
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
Node next = node.next;
//如果后继节点有效(非空且未取消)
if (next != null && next.waitStatus <= 0)
//CAS将前驱节点的下一个节点指向当前节点的下一个节点(即跳过当前节点)。这相当于将当前节点从队列链表中移除了
compareAndSetNext(pred, predNext, next);
} else {
//pred是头结点则调用 unparkSuccessor(node) 唤醒当前节点的后继
unparkSuccessor(node);
}
//将当前节点的下一个节点指向自身,防止内存泄漏,帮助垃圾回收器识别孤立节点
node.next = node; // help GC
}
}
AQS可中断获取锁
acquireInterruptibly方法
public final void acquireInterruptibly(int arg)
throws InterruptedException {
//前置检查,如果线程被标记过中断状态直接抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//先尝试获取锁,失败则执行doAcquireInterruptibly可中断的等待获取锁。
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
doAcquireInterruptibly方法
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
//线程包装为独占锁模式并加入等待队列
final Node node = addWaiter(Node.EXCLUSIVE);
//初始设为 true,只有成功获取锁时才设为 false
boolean failed = true;
try {
//自旋
for (;;) {
//取前驱节点p
final Node p = node.predecessor();
//只有当前节点的前驱是头节点时,才有资格尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
//是否在获取锁失败的时候挂起线程,返回 true 表示可以安全挂起则执行parkAndCheckInterrupt方法挂起线程。
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//如果线程在挂起或被唤醒状态中有被标记为中断,即parkAndCheckInterrupt方法返回true,则直接抛出异常,中断线程等待获取锁的操作。结束方法
throw new InterruptedException();
}
} finally {
//如果结束方法的时候没有获取到锁,将节点从同步队列中移除,因为该节点不再需要等待获取锁(中断/超时)
if (failed)
cancelAcquire(node);
}
}
释放锁
释放锁的步骤是通用的,请参考JAVA AQS队列源码解析第一章的出队方法源码解读部分。传送门:https://blog.csdn.net/weixin_38357164/article/details/157291871?fromshare=blogdetail&sharetype=blogdetail&sharerId=157291871&sharerefer=PC&sharesource=weixin_38357164&sharefrom=from_link
======================================
喜欢请点赞收藏加关注~~~
下一篇我们继续讲AQS队列第三章,敬请期待!!!
=======================================

更多推荐

所有评论(0)