谈谈你对AQS的了解

前言:今天无意中看到了"中华石衫"大佬的公众号的文章,觉得很不错,就摘录下来了。

1.写在前面:

这篇文章,我们来聊聊面试时一个比较有杀伤力的问题:聊聊你对AQS的理解?

之前有同学反馈,去互联网公司面试,面试官聊到并发时就问到了这个问题。当时那位同学内心估计受到了一万点伤害。。。因为首先,很多人可能连AQS是什么都不知道。或者仅仅是听说过AQS这个名词,但是可能连全称怎么拼写都不知道。更有甚者,可能会说:AQS?是不是一种思想?我们平时开发怎么来用AQS?总结起来,很多同学都对AQS有一种云里雾里的感觉,如果用搜索引擎查一下AQS是什么,估计看几篇文章就直接放弃了,因为密密麻麻的文字,实在是看不懂!所以基于上述痛点,这篇文章就用最简单的大白话配合N多张手绘图,给大家讲清楚AQS到底是什么?让各位同学面试被问到这个问题时,不至于不知所措。

二、ReentrantLock和AQS的关系

首先来看看,如果用java并发包下的ReentrantLock来加锁和释放锁,是个什么样的感觉?

这个学过java的同学应该都会吧,毕竟是java并发基本API的使用,我们直接看一下代码:

你这时可能会问,这个跟AQS有啥关系?关系大了去了!因为java并发包下很多API都是基于AQS来实现的加锁和释放锁等功能的,AQS是java并发包的基础类。举个栗子,比如说ReentrantLock、ReentrantReadWriteLock底层都是基于AQS来实现的。

那么AQS的全称是什么呢:AbstractQueuedSynchronizer,抽象队列同步器

给大家画一个图,看一下ReentrantLock和AQS之间的关系。

我们看上图,说白了,ReentrantLock内部包含了一个AQS对象,也就是AbstractQueuedSynchronizer类型的对象。这个AQS对象就是ReentrantLock可以实现加锁和释放锁的关键性的核心组件。

三、ReentrantLock加锁和释放锁的底层原理

好了,现在如果有一个线程过来尝试用ReentrantLock的lock()方法进行加锁,会发生什么事情?很简单,这个AQS对象内部有一个核心的变量叫做state,是int类型的,代表了加锁的状态。初始状态下,这个state的值是0。另外,这个AQS内部还有一个关键变量,用来记录当前加锁的是哪个线程,初始化状态下,这个变量是null。

接着线程1跑过来调用ReentrantLock的lock()方法尝试进行加锁,这个加锁的过程,直接就是用CAS操作将state值从0变为1。

(关于CAS,之前专门有文章做过详细阐述,大家可以自行阅读了解)

如果之前没人加过锁,那么state的值肯定是0,此时线程1就可以加锁成功。一旦线程1加锁成功了之后,就可以设置当前加锁线程是自己。所以大家看下面的图,就是线程1跑过来加锁的一个过程。

其实看到这儿,大家应该对所谓的AQS有感觉了。说白了,就是并发包里的一个核心组件,里面有state变量、加锁线程变量等核心的东西,维护了加锁状态。你会发现,ReentrantLock这种东西只是一个外层的API,内核中的锁机制实现都是依赖AQS组件的

这个ReentrantLock之所以用Reentrant打头,意思就是他是一个可重入锁

可重入锁的意思,就是你可以对一个ReentrantLock对象多次执行lock()加锁和unlock()释放锁,也就是可以对一个锁加多次,叫做可重入加锁。

大家看明白了那个state变量之后,就知道了如何进行可重入加锁!

其实每次线程1可重入加锁一次,会判断一下当前加锁线程就是自己,那么他自己就可以可重入多次加锁,每次加锁就是把state的值给累加1,别的没啥变化。

接着,如果线程1加锁了之后,线程2跑过来加锁会怎么样呢?我们来看看锁的互斥是如何实现的?

线程2跑过来一下看到,哎呀!state的值不是0啊?所以CAS操作将state从0变为1的过程会失败,因为state的值当前为1,说明已经有人加锁了!

接着线程2会看一下,是不是自己之前加的锁啊?当然不是了,“加锁线程”这个变量明确记录了是线程1占用了这个锁,所以线程2此时就是加锁失败。给大家来一张图,一起来感受一下这个过程:

接着,线程2会将自己放入AQS中的一个等待队列,因为自己尝试加锁失败了,此时就要将自己放入队列中来等待,等待线程1释放锁之后,自己就可以重新尝试加锁了。所以大家可以看到,AQS是如此的核心!AQS内部还有一个等待队列,专门放那些加锁失败的线程!

接着,线程1在执行完自己的业务逻辑代码之后,就会释放锁!他释放锁的过程非常的简单,就是将AQS内的state变量的值递减1,如果state值为0,则彻底释放锁,会将“加锁线程”变量也设置为null!整个过程,参见下图:

接下来,会从等待队列的队头唤醒线程2重新尝试加锁。

好!线程2现在就重新尝试加锁,这时还是用CAS操作将state从0变为1,此时就会成功,成功之后代表加锁成功,就会将state设置为1。此外,还要把“加锁线程”设置为线程2自己,同时线程2自己就从等待队列中出队了。最后再来一张图,大家来看看这个过程。

四、总结

OK,本文到这里为止,基本借着ReentrantLock的加锁和释放锁的过程,给大家讲清楚了其底层依赖的AQS的核心原理。基本上大家把这篇文章看懂,以后再也不会担心面试的时候被问到:谈谈你对AQS的理解这种问题了。其实一句话总结:AQS就是一个并发包的基础组件,用来实现各种锁,各种同步组件的。它包含了state变量、加锁线程、等待队列等并发中的核心组件。

AQS源码分析请转到:AQS源码分析

 

 

<think>嗯,用户让我谈谈AQS的理解。AQS是AbstractQueuedSynchronizer的缩写,对吧?这是Java并发编程中非常重要的一个类。我记得它是在java.util.concurrent.locks包下,由Doug Lea设计的。它的主要作用是为实现同步器提供框架,比如ReentrantLock、Semaphore这些常用的同步工具都是基于AQS构建的。 首先,我需要回顾一下AQS的核心思想。AQS使用了一个int类型的state变量来表示同步状态,通过内置的FIFO队列来管理线程的排队工作。这样,开发者不需要自己处理复杂的线程阻塞和唤醒机制,只需要关注state的获取和释放逻辑。 用户可能想知道AQS的工作原理。比如,state变量是如何被操作的?我记得它是通过CAS(Compare and Swap)操作来保证线程安全的。比如,当线程尝试获取锁时,会检查state的值,如果是0,表示锁没有被占用,然后通过CAS将state设为1,成功的话就获取锁;否则,线程会被放入队列中等待。 接下来,AQS中的队列是CLH队列的变种,这是一个双向队列,每个节点代表一个等待的线程。节点中包含了线程的引用以及等待状态等信息。当线程无法获取锁时,会被构造成节点加入队列尾部,并在适当的时候被唤醒。 然后,用户可能关心的是AQS如何实现不同的同步器。比如,独占模式和共享模式的区别。独占模式下,只有一个线程能获取资源,如ReentrantLock;而共享模式下,多个线程可以同时获取,如Semaphore。AQS通过不同的模板方法来实现这两种模式,比如tryAcquire和tryAcquireShared。 我还需要提到AQS的模板方法模式。开发者需要重写特定的方法,比如tryAcquire、tryRelease、tryAcquireShared、tryReleaseShared等,来定义自己的同步逻辑。而AQS会处理线程的排队、阻塞和唤醒,这样开发者不需要关心底层的队列管理。 可能用户会问,为什么AQS这么重要?因为它抽象出了同步器的核心部分,让开发者可以更专注于业务逻辑,而不必重复实现复杂的线程调度。这也是Java并发包中许多工具高效可靠的原因。 另外,可重入性是如何实现的?比如ReentrantLock,当同一个线程多次获取锁时,state会递增,每次释放递减,直到为0才完全释放。这需要在tryAcquire和tryRelease方法中处理当前线程的判断和state的增减。 还有,AQS如何处理中断和超时?比如tryAcquireNanos方法允许线程在指定时间内获取锁,超时则返回false。而acquireInterruptibly方法支持响应中断,这些都需要在节点状态和唤醒机制中处理。 可能我还需要举几个例子,比如CountDownLatch,它的内部类Sync继承自AQS,初始化时state设为计数器的值,每次countDown()调用releaseShared,而await()调用acquireShared,直到state为0。 不过,用户可能对底层实现细节感兴趣,比如节点的入队和出队过程,如何确保线程安全,以及自旋和阻塞的平衡。比如,在入队时,可能使用CAS来保证原子性,而为了避免过多的CPU自旋,可能会在几次尝试后进入阻塞状态,依赖前驱节点的信号来唤醒。 还有,取消节点的情况如何处理?比如在获取锁超时或中断时,节点需要从队列中移除,并重新调整队列的状态,确保后续节点能正确被唤醒。 可能用户还会问到公平与非公平锁的区别。在AQS中,公平锁严格按照队列顺序获取锁,而非公平锁允许插队,新来的线程可能先尝试获取锁,而不是直接排队,这样提高了吞吐量但可能导致饥饿。 总结一下,AQS通过模板方法将同步状态的管理和线程排队机制抽象出来,让开发者可以灵活实现各种同步器,同时保证了高效和可靠性。理解AQS对于掌握Java并发编程至关重要,它是许多高级同步工具的基础。</think>AQS(AbstractQueuedSynchronizer)是 Java 并发编程的核心基础框架,用于构建锁和其他同步工具(如 `ReentrantLock`、`Semaphore`、`CountDownLatch` 等)。其核心思想是通过 **共享资源的状态管理** 和 **线程排队机制** 实现高效的线程同步。以下是关键点的总结: --- ### **1. 核心思想** - **同步状态(`state`)**: 通过一个 `volatile int` 变量表示资源状态(如锁的持有次数、信号量剩余量等),通过 CAS(Compare and Swap)操作保证原子性修改。 - **队列管理**: 使用一个 **CLH 变体的双向队列**(FIFO)管理竞争资源的线程。队列中的每个节点(`Node`)封装了等待线程的信息及其等待状态(如是否被取消)。 --- ### **2. 核心机制** #### **(1) 资源访问模式** - **独占模式(Exclusive)**: 资源仅允许一个线程持有(如 `ReentrantLock`)。通过 `tryAcquire` 和 `tryRelease` 定义获取/释放逻辑。 - **共享模式(Shared)**: 资源允许多个线程共享(如 `Semaphore`、`CountDownLatch`)。通过 `tryAcquireShared` 和 `tryReleaseShared` 实现。 #### **(2) 线程阻塞与唤醒** - 当线程获取资源失败时,会被封装为 `Node` 加入队列尾部,并通过 `LockSupport.park()` 阻塞。 - 当资源释放时,AQS 按队列顺序唤醒后续节点中的线程(公平性由实现决定)。 --- ### **3. 关键实现细节** #### **(1) 模板方法模式** 开发者需重写以下方法定义资源管理逻辑: - **独占模式**:`tryAcquire(int arg)`、`tryRelease(int arg)` - **共享模式**:`tryAcquireShared(int arg)`、`tryReleaseShared(int arg)` - **是否被当前线程独占**:`isHeldExclusively()` #### **(2) 可重入性** - 如 `ReentrantLock` 通过记录当前持有锁的线程和 `state` 重入次数实现。每次重入时 `state` 递增,释放时递减至 0 才完全释放资源。 #### **(3) 公平性与非公平性** - **公平锁**:严格按队列顺序分配资源,避免饥饿。 - **非公平锁**:允许新线程直接尝试插队获取资源(提高吞吐量)。 --- ### **4. 典型应用** - **ReentrantLock**: 基于 AQS 实现可重入的独占锁,支持公平/非公平策略。 - **Semaphore**: 通过共享模式控制并发线程数,`state` 表示可用许可证数量。 - **CountDownLatch**: `state` 初始化为计数器值,线程调用 `countDown()` 递减,`await()` 阻塞直到 `state` 归零。 --- ### **5. 优势与意义** - **解耦同步逻辑**:AQS 封装了线程排队、阻塞/唤醒等底层操作,开发者只需关注资源状态的管理。 - **高性能**:通过自旋、CAS 和队列优化减少上下文切换。 - **扩展性**:基于模板方法模式,可灵活实现各种同步工具。 --- ### **6. 示例:实现一个简易锁** ```java public class MyLock extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int arg) { if (compareAndSetState(0, 1)) { // CAS 尝试获取锁 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() { acquire(1); } public void unlock() { release(1); } } ``` --- ### **总结** AQS 是 Java 并发包的基石,通过将复杂的线程同步问题抽象为 **状态管理** 和 **队列调度**,提供了高度灵活且高效的同步框架。理解其原理对掌握 `ReentrantLock`、`ThreadPoolExecutor` 等工具至关重要,也是优化高并发程序的关键。
评论 18
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值