AQS简单学习

本文详细探讨了AQS(AbstractQueuedSynchronizer)的作用和原理,包括同步状态管理、线程FIFO队列以及子类需要实现的获取与释放方法。AQS作为构建锁和同步器的基础,简化了线程协作的复杂性,如在ReentrantLock、Semaphore和CountDownLatch中的应用。通过自定义获取和释放方法,读者可以理解如何基于AQS构建自己的线程同步类。

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

抽象队列同步器 AbstractQueuedSynchronizer,是一个用于构建锁、同步器、协作的工具类或者框架。

AQS的源码还是比较复杂的,所以应该先理解其作用,场景,再去理解他的结构,原理。

为什么要有AQS?
我们学过的ReentrantLock和semaphore都有共同点,类似个闸门,只允许一定数量的线程通过。还都是同步的,底层都是AQS.
他们的原理里都有个继承AQS的内部类

AQS的比喻 — 面试
一群人去面试,HR要安排签到、叫号、先来后到等工作,其实就类似与AQS。
而面试官不用关心两个面试者是否号码冲突,也不用管他们的排队等待,也不用管叫号唤醒这些都交给AQS去做。

面试官只要说出自己的需求如群面还是单面,如信号量类似于单面进一个出一个,CountDownLatch是群面,等人都到齐了才开始。剩下的唤醒啥的都交给HR。

总结来说AQS的工作:

  • 同步状态state的原子性管理,基于state控制线程的入队阻塞与唤醒
  • 阻塞队列的管理
    有了AQS我们可以只用关注业务逻辑,甚至可以自己实现自己的线程协作类

java中AQS的实现类:
主要是线程池里的Worker、Reentrantlock里的公平非公平锁、读写锁里的公平非公平锁、Semphore里的公平非公平锁、CountDownLatch里的锁。
在这里插入图片描述

AQS源码原理

1. state 同步状态

private volatile int state;
//使用系统的CAS指令来实现compareAndSetState方法

state在每个实现类里的意义都不同:

  • 在Semaphore表示剩余的许可证数量
  • 在CountDownLatch里代表还需要倒数的数量
  • 在ReenTrantLock里代表锁的占用情况,如可重入计数,0表示不被任何线程占有。

2. 控制线程抢锁和配合的FIFO队列

AQS会维护一个等待的线程队列 ,把线程都放到这个队列里,AQS进行排队管理。
在这里插入图片描述
当多个线程争用同一把锁的时候,必须有排队机制将那些没有拿到锁的线程串在一起;当锁释放的时候,锁管理器就会挑选一个线程来占有这个刚释放的锁

3. 需要子类去实现的获取、释放等方法

AQS里有很多protected修饰的方法,需要子类自定义实现的

获取方法:
需要依赖state变量,经常会阻塞。比如ReenTrantLock看state不是0就阻塞等待

释放方法:
在Semaphore,释放就是release方法,作用是释放一个许可证
在CountDownLatch里,释放就是countDown方法 ,作用倒数一个数即state-1

自己实现一个AQS

  1. 写一个类,想好协作的逻辑,实现获取/释放方法。
  2. 然后内部写一个类继承AbstractQueuedSynchronizer,最后根据想实现的是一个独占,就重写TryAcquire/TryRelease,共享的就实现TryAcquireShared/TryReleaseShared,
  3. 在第一步里的获取和释放方法里去调用内部类的acquire、release或者shared方法。
/**
 * 这是一个类似于CountDownLatch的闩锁类,只不过个数为1,只需要触发signal即可。
 * 因为闩锁是共享性的,所以它使用shared获取和释放方法
 * <p>
 * 代码取自AQS源码注释中举例
 */
public class OneShotLatch {

    private Sync sync = new Sync();

    public void await() throws InterruptedException {
        // 调用AQS的acquireShared,这里的参数没有使用到,实际没啥意义
        sync.acquireSharedInterruptibly(1);
    }

    public void signal() {
        // 调用AQS的releaseShared
        sync.releaseShared(1);
    }


    private static class Sync extends AbstractQueuedSynchronizer {

        /**
         * if (tryAcquireShared(arg) < 0)
         * doAcquireSharedInterruptibly(arg);
         *
         * @return -1就会进入阻塞队列,1就是获取锁成功
         */
        @Override
        protected int tryAcquireShared(int arg) {
            // state int默认是0,不等于0说明release方法被执行了
            // 线程这里getState()不是0,就返回1,根据代码就不会进入阻塞队列,就相当于获取到锁了
            return getState() != 0 ? 1 : -1;
        }

        /**
         * @return true代表应该唤醒队列中的线程了,false代表还没放闸应该进入阻塞队列等待
         */
        @Override
        protected boolean tryReleaseShared(int arg) {
            setState(1);
            return true;
        }
    }


    public static void main(String[] args) {
        OneShotLatch oneShotLatch = new OneShotLatch();

        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName() + " 尝试获取latch。。。。");
                try {
                    oneShotLatch.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + " 开闸放行");
            }).start();
        }

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 开闸,不像countDownLatch需要每个线程倒数
        oneShotLatch.signal();
    }
}
<think>好的,我现在需要回答用户关于Java中AQS的使用和原理的问题。用户之前已经询问过Redis分布式锁的实现,现在转向并发编程中的AQS,这是一个比较深入的主题。我需要结合用户提供的站内引用,确保回答准确且符合用户的需求。 首先,我要理解用户的需求。用户明确想了解AQS的使用和原理,可能需要详细的步骤和示例。用户提供的引用内容涵盖了AQS的核心方法、实现步骤、继承关系以及基本原理,这些都需要整合到回答中。 接下来,我需要组织回答的结构。通常,技术原理的解析可以分为概述、核心组成、工作原理、使用步骤、源码解析、应用场景和常见问题等部分。根据用户提供的引用,特别是引用[2]中的JUC用法套路,可以分步骤说明如何实现AQS的子类。 在引用中,提到AQS的关键方法如tryAcquire、tryRelease等,以及子类继承AQS的步骤,这些都需要详细说明。例如,用户引用[1]中列出了五个模板方法,必须解释每个方法的作用和如何重写。同时,引用[4]提到AQS通过CLH队列管理线程阻塞,这部分需要解释其工作原理。 需要注意用户之前的对话历史,用户可能已经熟悉分布式锁的实现,但AQS属于底层同步框架,可能需要从基础讲起,但保持一定的深度。此外,用户可能希望看到代码示例,例如如何实现一个自定义的锁或同步器,这可以通过引用[2]中的步骤来展示。 同时,要确保回答符合用户要求的格式:使用中文,行内公式用$...$,独立公式用$$...$$,并添加相关问题。引用需要自然标注,如[^1][^2]等。 现在需要检查是否有遗漏的关键点。例如,AQS的state变量管理、CLH队列的实现、独占模式和共享模式的区别,以及如何通过AQS实现常见的同步工具如ReentrantLock、Semaphore等。这些内容在用户提供的引用中都有涉及,需要整合到回答中。 此外,用户可能对AQS的底层数据结构感兴趣,比如CLH队列变体的工作原理,节点状态(CANCELLED、SIGNAL等)的管理,这些在引用[3]和[4]中提到,需要简要说明。 最后,生成的相关问题需要与AQS相关,比如与ReentrantLock的关系、公平与非公平锁的实现、AQS的性能优化等,确保问题具有针对性,帮助用户进一步学习。 总结回答的结构: 1. AQS概述:定义、作用、设计目标。 2. 核心组成:state变量、CLH队列、模板方法。 3. 工作原理:获取和释放资源的流程,独占与共享模式。 4. 使用步骤:如何继承AQS并实现自定义同步器。 5. 源码解析:关键方法如acquire、release的内部实现。 6. 应用场景:举例说明AQS在JUC中的应用。 7. 常见问题:如重入实现、公平性控制等。 在写作过程中,要确保每个部分都参考了用户提供的引用内容,并正确标注引用来源。例如,在解释模板方法时引用[1],在实现步骤中引用[2],在CLH队列部分引用[3][4]等。 需要特别注意避免技术错误,例如正确区分独占和共享模式的方法,正确描述state变量的使用,以及CLH队列的工作机制。同时,代码示例要简明,如展示一个简单的Mutex实现,帮助用户理解如何重写tryAcquire和tryRelease。 最后,确保语言流畅,结构清晰,符合用户要求的系统级指令,如正确使用LaTeX格式,添加相关问题,引用标注自然。</think>### Java AQS 使用与原理详解 --- #### 一、AQS 核心概述 **AbstractQueuedSynchronizer(AQS)** 是 Java 并发包(JUC)的底层同步框架,为构建锁(如 `ReentrantLock`)和同步器(如 `Semaphore`、`CountDownLatch`)提供了一套通用模板。其核心思想是通过 **CLH 队列**(变体的双向链表)管理线程阻塞与唤醒,通过 **状态变量**(`state`)控制资源访问。 --- #### 二、AQS 核心组成 1. **状态变量 `state`** - 表示共享资源的状态(如锁的重入次数、信号量剩余许可数)。 - 通过 `volatile` 保证可见性,通过 CAS 操作保证原子性修改[^4]。 2. **CLH 队列** - 由 `Node` 节点构成的队列,存储等待线程。 - 每个节点包含线程引用、等待状态(如 `CANCELLED`、`SIGNAL`)和前后指针[^3]。 3. **模板方法模式** AQS 定义 **5 个需子类实现的方法**,控制资源获取与释放逻辑[^1]: ```java protected boolean tryAcquire(int arg) // 独占模式获取资源 protected boolean tryRelease(int arg) // 独占模式释放资源 protected int tryAcquireShared(int arg) // 共享模式获取资源 protected boolean tryReleaseShared(int arg) // 共享模式释放资源 protected boolean isHeldExclusively() // 是否独占资源(Condition 相关) ``` --- #### 三、AQS 工作原理 ##### 1. 独占模式(如 `ReentrantLock`) - **资源获取**: ```java public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } ``` 1. 调用 `tryAcquire` 尝试直接获取资源。 2. 失败则创建独占节点加入队列,通过自旋+CAS 竞争资源。 3. 若线程被中断,恢复中断状态[^4]。 - **资源释放**: ```java public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); // 唤醒后继节点 return true; } return false; } ``` ##### 2. 共享模式(如 `Semaphore`) - 类似独占模式,但允许多线程同时获取资源,通过 `tryAcquireShared` 返回剩余资源数。 - 典型场景:`CountDownLatch` 的 `countDown()` 和 `await()` 方法。 --- #### 四、实现自定义同步器的步骤(参考 JUC 用法[^2]) 1. **定义同步器类**,内部继承 `AbstractQueuedSynchronizer`: ```java public class MyLock { private final Sync sync = new Sync(); private static class Sync extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int arg) { // 实现资源获取逻辑 if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } @Override protected boolean tryRelease(int arg) { if (getExclusiveOwnerThread() != Thread.currentThread()) throw new IllegalMonitorStateException(); setExclusiveOwnerThread(null); setState(0); return true; } } public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } } ``` 2. **选择模式**:根据需求选择 **独占** 或 **共享** 模式,重写对应方法。 3. **调用 AQS 核心方法**: - 在自定义方法中调用 `acquire()`、`release()` 或 `acquireShared()`、`releaseShared()`。 --- #### 五、源码级关键设计 1. **CLH 队列优化** AQS 使用 **虚拟头节点** 简化队列操作,通过 `prev` 和 `next` 指针维护线程等待顺序。 2. **状态变量 `state` 的 CAS 操作** ```java protected final boolean compareAndSetState(int expect, int update) { return unsafe.compareAndSwapInt(this, stateOffset, expect, update); } ``` 3. **公平性与非公平性控制** - **公平锁**:`tryAcquire` 检查队列是否有等待线程。 - **非公平锁**:直接竞争资源,可能插队(如 `ReentrantLock` 默认模式)[^1]。 --- #### 六、AQS 应用场景 1. **互斥锁**(`ReentrantLock`) 2. **信号量**(`Semaphore`) 3. **倒计时门闩**(`CountDownLatch`) 4. **循环屏障**(`CyclicBarrier`) 5. **读写锁**(`ReentrantReadWriteLock`) --- #### 七、常见问题解决方案 1. **可重入性实现** 在 `tryAcquire` 中检查当前线程是否已持有锁,并更新 `state` 值: ```java if (getExclusiveOwnerThread() == currentThread()) { setState(getState() + 1); // 重入次数+1 return true; } ``` 2. **超时获取锁** 使用 `tryAcquireNanos(int arg, long nanosTimeout)` 方法,结合自旋和 `LockSupport.parkNanos()`。 3. **条件变量(Condition)** AQS 内部类 `ConditionObject` 实现条件队列,通过 `await()` 和 `signal()` 管理线程等待与唤醒。 --- $$ \text{性能公式:吞吐量} = \frac{\text{有效资源操作次数}}{\text{总时间}} \propto \text{CLH 队列调度效率} $$ --- ### 相关问题 1. AQS 如何通过 `state` 变量实现不同同步器(如信号量与锁)? 2. 公平锁与非公平锁在 AQS 中的具体实现差异是什么? 3. AQS 的 `ConditionObject` 如何与 CLH 队列协作? 4. 为什么 AQS 使用 CLH 队列变体而非原始 CLH 队列? 5. 在高并发场景下,AQS 可能存在哪些性能瓶颈?如何优化? --- 以上内容综合参考了 AQS 的模板方法设计[^1]、JUC 实现套路[^2]、CLH 队列原理[^3] 和资源竞争机制[^4]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值