Java并发系列 | AbstractQueuedSynchronizer源码分析之独占模式
发布日期:2021-05-19 16:45:05 浏览次数:17 分类:精选文章

本文共 4812 字,大约阅读时间需要 16 分钟。

在上一篇《Java并发系列[1]----AbstractQueuedSynchronizer源码分析之概要分析》中,我们介绍了AbstractQueuedSynchronizer(AQS)基本的概念,重点阐述了AQS的排队区实现方式,包括独占模式和共享模式的概念以及结点的等待状态。掌握这些内容是后续分析AQS源码的基础,因此建议读者在阅读本文之前回顾上一篇文章。

本文将重点探讨在独占模式下,结点如何进入同步队列排队,以及离开同步队列之前的操作流程。

AQS为在独占模式和共享模式下提供三种获取锁的方式:不响应线程中断的获取、响应线程中断的获取以及设置超时时间的获取。这三种方式在实现上大致相同,区别仅在于细节处理部分。本文将以不响应线程中断的方式为例,重点分析其他两种方式的不同之处。

1、不响应线程中断的锁获取方式

不响应线程中断的获取方式主要通过acquire方法实现,代码如下:

public final void acquire(int arg) {
if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}

该方法由四个步骤组成:首先尝试获取锁,未能成功后进入排队区,最后可能自中断。

第一步:尝试获取锁

protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}

这个方法是子类实现的,用于判断锁是否可用。这一步相当于敲门,如果门没锁(tryAcquire返回true),就直接进入,否则进入下一步。

第二步:进入排队区

private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
enq(node);
return node;
}

这步将当前线程包装成结点,并添加到同步队列尾部。如果尾结点存在,新增结点的同时调整前后指针;否则,初始化同步队列并将结点作为新尾结点。

第三步:获取排队成功

final boolean acquireQueued(Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // 协助GC
failed = false;
return interrupted; // 唤醒后续操作
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
interrupted = true;
}
}
} finally {
if (failed) {
cancelAcquire(node);
}
}
}

进入排队区后,这个方法会尝试在同步队列中寻找可获取锁的位置。如果前继结点是头结点且尝试获取锁成功,则头结点更新为当前结点,前继结点指针清空。若获取锁失败,判断是否需要挂起当前线程,决定是否使用parkAndCheckInterrupt方法进行挂起。

第四步:自中断

private static void selfInterrupt() {
Thread.currentThread().interrupt();
}

如果在获取锁过程中没有成功获取锁,线程会被中断挂起。

2、响应线程中断的锁获取方式

响应线程中断的方式通过doAcquireInterruptibly方法实现,代码如下:

private void doAcquireInterruptibly(int arg) throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt()) {
throw new InterruptedException();
}
}
} finally {
if (failed) {
cancelAcquire(node);
}
}
}

与不响应线程中断的方式相比,响应线程中断方式在获取锁失败时会抛出InterruptedException异常,这样线程可以及时被中断并处理。

3、设置超时时间的锁获取方式

设置超时时间的方式通过doAcquireNanos方法实现,代码如下:

private boolean doAcquireNanos(int arg, long nanosTimeout) throws InterruptedException {
long lastTime = System.nanoTime();
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null;
failed = false;
return true;
}
if (nanosTimeout <= 0) {
return false;
}
if (shouldParkAfterFailedAcquire(p, node) && nanosTimeout > spinForTimeoutThreshold) {
LockSupport.parkNanos(this, nanosTimeout);
}
long now = System.nanoTime();
nanosTimeout -= now - lastTime;
lastTime = now;
if (Thread.interrupted()) {
throw new InterruptedException();
}
}
} finally {
if (failed) {
cancelAcquire(node);
}
}
}

超时设置锁获取方式会在超时时间到达时退出循环或挂起线程。每次循环会减少超时时间,并检查线程是否中断。

4、线程释放锁并离开同步队列的方式

释放锁的过程主要通过release方法实现,代码如下:

public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0) {
unparkSuccessor(h);
}
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
if (node.waitStatus < 0) {
compareAndSetWaitStatus(node, node.waitStatus, 0);
}
Node s = node.next;
if (s == null || s.waitStatus > 0) {
for (Node t = tail; t != node; t = t.prev) {
if (t.waitStatus <= 0) {
s = t;
break;
}
}
if (s != null) {
LockSupport.unpark(s.thread);
}
}
}

当线程持有锁时,它会尝试释放锁。如果头结点不为空且前一直状态不为0,则唤醒后继结点。唤醒后继结点时,会通知线程后续是否有需要继续挂牌的情况。

总结

AQS为Java并发编程提供了强大的工具之一,其独占模式下的获取和释放锁机制通过同步队列实现,允许线程在不响应中断或响应中断的条件下进行获取。理解这些机制对深入掌握并发编程至关重要。

上一篇:Java流及流操作示例
下一篇:Java并发系列 | AbstractQueuedSynchronizer源码分析之共享模式

发表评论

最新留言

初次前来,多多关照!
[***.217.46.12]2025年05月12日 12时34分59秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章