AQS核心流程解析-release方法

本文深入探讨了Java并发库AQS的release方法,分析了其内部逻辑,包括tryRelease的执行、条件判断及unparkSuccessor的唤醒机制。通过对节点状态的CAS操作,确保线程安全地释放锁并唤醒等待的线程。讨论了为何从后向前遍历寻找可唤醒的后继节点,避免并发问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在分析过acquire方法之后,这次来看看与之对应的release方法:

    public final boolean release(long arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease(arg)方法是由子类实现的,方法返回true表示释放成功,下一步就是从队列中将线程释放掉,并且唤醒同步队列中的第一个线程的线程。
由于解锁是从头节点开始的,所以取Node h = head,然后的判断条件:if (h != null && h.waitStatus != 0),我们可以分析一下可能的几种情况:

  1. head = null
    head 还未初始化。初始情况下,head = null,当第一个节点入队后,head 会被初始为一个虚拟(dummy)节点。这里,如果还没节点入队就调用 release 释放同步状态,就会出现 h = null 的情况
  2. head != null && waitStatus = 0:表明后继节点对应的线程仍在运行中,不需要唤醒
  3. head != null && waitStatus < 0:后继节点对应的线程可能被阻塞了,需要唤醒

当满足情况3的时候,进入了unparkSuccessor方法:

    private void unparkSuccessor(Node node) {
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);
        Node s = node.next;
        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);
    }

首先分析下compareAndSetWaitStatus(node, ws, 0);的作用,表面上就是将当前释放锁节点的waitStatus改成0,但是这里存在两个疑问:1. 为什么要改成0,2:为什么用CAS的方式改;

要搞清楚这两点,就要从shouldParkAfterFailedAcquire的代码中找线索,判断是否阻塞的一个关键因素,就是对前置节点的waitStatus状态进行判断,如果前置节点ws == Node.SIGNAL直接阻塞,否则的话先将前置节点compareAndSetWaitStatus(pred, ws, Node.SIGNAL);,然后进入下一次自旋。unparkSuccessor将状态改成0,是因为当前节点已经释放锁了,那么后续节点不需要阻塞了,不想让后续节点进入ws == Node.SIGNAL的分支中。

那么,为什么要用CAS的方式呢,我们首先要有个理念,就是线程的环境是瞬息万变的,unparkSuccessorcompareAndSetWaitStatus(node, ws, 0);shouldParkAfterFailedAcquirecompareAndSetWaitStatus(pred, ws, Node.SIGNAL);是有可能同时执行的,而且他们操作的有可能是同一个节点的数据,那么就会有线程竞争,所以需要CAS,解决并发问题。

我们假设队列中只有两个节点:

Created with Raphaël 2.2.0head指针ABtail指针

B的shouldParkAfterFailedAcquire先执行完,将A的ws改成SIGNAL进入自旋,然后A.unparkSuccessor执行完改回0,此时B即使进入shouldParkAfterFailedAcquire也不会阻塞,而是进入下一次循环,这一次循环不同的是,锁已经被释放,并且A是头节点,那么B执行到acquireQueuedif (p == head && tryAcquire(arg)) {就能顺利获取到锁,B成功晋级成头节点(执行setHead(node);)

所以要搞清楚unparkSuccessor的这行代码,需要带着加锁的逻辑一起看,不然没法"串联"起来。

剩下的代码其实含义很明确:找到后面的第一个不是CANCLE状态的线程,唤醒他。这个遍历的过程是从后往前的,很奇怪吧,为什么不从前往后的,非要反着来?这里还是"瞬息万变"的理念,你想想,如果在遍历之前,突然有一个线程加入进来,添加到尾部,会有一个瞬间状态是这样的:
加入尾节点的瞬间态
如果从前往后,那么就无法遍历到n4节点。

<think>好的,我现在需要为用户解析Java中的AQS(AbstractQueuedSynchronizer)。首先,我得回忆一下AQS的基本概念和作用。AQSJava并发包中的一个核心框架,很多同步器比如ReentrantLock、Semaphore都是基于它实现的。用户可能想了解它的工作原理、结构以及如何使用。 接下来,我应该从AQS的设计目的说起,解释它如何通过一个FIFO队列来管理线程的阻塞和唤醒。然后,需要提到它使用了一个volatile的int变量来表示同步状态,这是实现各种同步机制的基础。 然后,结构部分要分点说明,包括同步状态、CLH队列、节点类型(独占和共享)。每个部分要简明扼要,比如同步状态是通过getState()、setState()和compareAndSetState()方法来操作的。 接下来,关键方法部分,用户可能想知道如何自定义同步器,所以需要提到tryAcquire、tryRelease方法,并举例说明独占模式和共享模式的区别。比如CountDownLatch和Semaphore的不同实现方式。 然后,内部实现需要详细一点,比如CLH队列的变种,节点如何入队和出队,以及自旋和阻塞的机制。这部分可能需要解释acquireQueued方法中的循环和park操作。 应用场景部分,列举常见的基于AQS的类,帮助用户理解其实际用途。比如ReentrantLock、ReentrantReadWriteLock等,说明它们各自的特点和如何利用AQS实现的。 最后,自定义同步器的步骤,给出一个简单的例子,比如实现一个非重入的独占锁,展示如何重写tryAcquire和tryRelease方法。这样用户能更直观地理解如何应用AQS。 需要注意的是,用户可能对某些技术细节不太熟悉,比如CLH队列或者CAS操作,所以在解释时要避免过于深入,保持通俗易懂。同时,确保代码示例正确,符合Java的语法和AQS的使用规范。 另外,要检查是否所有数学公式和代码都按照要求格式化,行内公式用$...$,独立公式用$$...$$,虽然这个问题可能不涉及太多数学内容,但如果有状态表示之类的可能需要用LaTeX。不过在这个问题中,可能主要是代码和术语,所以重点放在结构清晰和正确性上。 最后,总结AQS的重要性和优势,强调其作为Java并发基础组件的作用,帮助用户形成整体的理解。</think>Java中的AQS(AbstractQueuedSynchronizer)是并发编程的核心框架,用于构建锁和同步器。以下是逐步解析--- ### **1. AQS的作用** AQS通过**同步状态管理**和**线程排队机制**,为开发者提供了实现**独占锁**(如ReentrantLock)和**共享锁**(如Semaphore)的底层支持。 --- ### **2. 核心结构** AQS依赖两个关键组件: - **同步状态(State)** 用`volatile int`表示,通过以下方法操作: ```java getState() // 获取当前状态 setState(int) // 直接设置状态 compareAndSetState(int expect, int update) // CAS原子操作 ``` - **CLH队列** 一个FIFO双向队列,存储等待线程的节点(Node)。 --- ### **3. 节点类型(Node)** 每个线程被封装为队列节点,包含两种模式: - **独占模式(EXCLUSIVE)** 同一时刻仅允许一个线程获取资源(如ReentrantLock)。 - **共享模式(SHARED)** 允许多个线程同时获取资源(如Semaphore)。 --- ### **4. 关键方法** 开发者需重写以下方法实现自定义同步器: - **独占模式** ```java protected boolean tryAcquire(int arg) // 尝试获取资源 protected boolean tryRelease(int arg) // 尝试释放资源 ``` - **共享模式** ```java protected int tryAcquireShared(int arg) // 返回值≥0表示成功 protected boolean tryReleaseShared(int arg) ``` --- ### **5. 内部工作流程** #### **获取资源(以独占模式为例)** 1. 调用`tryAcquire`尝试直接获取资源。 2. 若失败,将线程封装为Node加入队列尾部。 3. 进入自旋循环,检查前驱节点是否为头节点: - 是则再次尝试`tryAcquire`。 - 否则调用`LockSupport.park()`阻塞线程。 #### **释放资源** 1. 调用`tryRelease`释放资源。 2. 若成功,唤醒队列中下一个有效节点。 --- ### **6. 应用场景** AQS是以下同步器的基础: - **ReentrantLock** 通过重写`tryAcquire`实现可重入锁。 - **Semaphore** 使用共享模式控制资源访问数量。 - **CountDownLatch** 利用共享模式实现线程等待。 - **ReentrantReadWriteLock** 分离读(共享)和写(独占)锁。 --- ### **7. 自定义同步器示例** 实现一个非重入的独占锁: ```java class NonReentrantLock extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int acquires) { if (compareAndSetState(0, 1)) { // CAS设置状态 setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } @Override protected boolean tryRelease(int releases) { if (getState() == 0) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); // 无需CAS,只有持有线程能调用release return true; } } ``` --- ### **8. 优势与总结** - **灵活性**:通过重写少数方法即可实现复杂同步逻辑。 - **高性能**:CLH队列减少锁竞争,CAS操作保证原子性。 - **标准化**:JDK内置同步器均基于AQS,代码复用性高。 AQSJava并发包的基石,理解其原理能帮助开发者更高效地处理多线程问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值