说说 AQS 吧?
说说 AQS 吧?
回答重点
如果面试官问你为什么需要 AQS ,不要长篇大论,容易把自己和面试官绕进去。
就这样简要的回答:
简单来说 AQS 就是起到了一个抽象、封装的作用,将一些排队、入队、加锁、中断等方法提供出来,便于其他相关 JUC 锁的使用,具体加锁时机、入队时机等都需要实现类自己控制。
它主要通过维护一个共享状态(state)和一个先进先出(FIFO)的等待队列,来管理线程对共享资源的访问。
state 用 volatile 修饰,表示当前资源的状态。例如,在独占锁中,state 为 0 表示未被占用,为 1 表示已被占用。
当线程尝试获取资源失败时,会被加入到 AQS 的等待队列中。这个队列是一个变体的 CLH 队列,采用双向链表结构,节点包含线程的引用、等待状态以及前驱和后继节点的指针。
AQS 常见的实现类有 ReentrantLock、CountDownLatch、Semaphore 等等。
然后面试官会引申问你具体 ReentrantLock 的实现原理是怎样的呢?
参见这题:ReentrantLock 原理 。
AQS 扩展知识
AQS 的核心机制
1)状态(State):
AQS 通过一个 volatile 类型的整数 state 来表示同步状态。
子类通过 getState()、setState(int) 和 compareAndSetState(int, int) 方法来检查和修改该状态。
状态可以表示多种含义,例如在 ReentrantLock 中,状态表示锁的重入次数;在 Semaphore 中,状态表示可用的许可数。
2)队列(Queue):
AQS 维护了一个 FIFO 的等待队列,用于管理等待获取同步状态的线程。每个节点(Node)代表一个等待的线程,节点之间通过 next 和 prev 指针链接。
1 | static final class Node { |
当一个线程获取同步状态失败时,它会被添加到等待队列中,并自旋等待或被阻塞,直到前面的线程释放同步状态。
3)独占模式和共享模式:
- 独占模式:只有一个线程能获取同步状态,例如 ReentrantLock。
- 共享模式:多个线程可以同时获取同步状态,例如 Semaphore 和 ReadWriteLock。
简单俯瞰 AQS 框架
AQS 整体架构图-(图来自美团技术 ):
一般实现类仅需重写图中的 API 层,来控制加锁和入队等时机(仅关注这层就够了,其他 AQS 都封装好了)。具体如何获取到锁由上图第二层的方法提供,如果未获取到锁,则进入排队层。然后上述从 API 层到排队层都依赖数据层提供的支持。
关键方法介绍,子类通过重写这些方法来实现特定的同步器:
1)tryAcquire(int arg):
尝试以独占模式获取同步状态。由子类实现,返回 true 表示获取成功,返回 false 表示获取失败。
2)tryRelease(int arg):
尝试以独占模式释放同步状态。由子类实现,返回 true 表示释放成功,返回 false 表示释放失败。
3)tryAcquireShared(int arg):
尝试以共享模式获取同步状态。由子类实现,返回一个非负数表示获取成功,返回负数表示获取失败。
4)tryReleaseShared(int arg):
尝试以共享模式释放同步状态。由子类实现,返回 true 表示释放成功,返回 false 表示释放失败。
5)isHeldExclusively():
判断当前线程是否以独占模式持有同步状态。由子类实现,返回 true 表示当前线程持有同步状态,返回 false 表示没有持有。
简单基于 AQS 实现一个独占锁
定义一个 Sync 继承 AQS 自定义 tryAcquire、tryRelease、isHeldExclusively 即可实现一个独占锁,非常简单。所以说 AQS 把一切都封装的很好,基于 AQS 可以便于其他相关 JUC 锁的使用!
1 |
|
ReentrantLock 对 AQS 的使用
简单基于 AQS 实现一个独占锁之后,我们来看看 ReentrantLock 是如何基于 AQS 实现的。
可以看到,同样是定义一个 Sync 继承 AQS 自定义 tryAcquire、tryRelease、isHeldExclusively 等方法:
先简单分析下 nonfairTryAcquire 尝试申请锁的方法,看源码注释应该就很清晰了。本质就是 CAS 修改 state 并且实现了可重入:
再看下 tryRelease 尝试释放锁的方法:
如果有重入或者共享状态,那么来一个线程则可以将 state + 1,同理释放的时候将 state - 1,如果 state 为 0 说明线程已经不需要这把锁了,此时尝试释放锁即可。
所以本质还是对 state 的修改!
再看一下 ReentrantLock 尝试公平锁的加锁实现:
再看下 ReentrantLock 强制加锁,如果未抢到则入队的过程:
源码中 selfInterrupt 的作用是什么?
这里需要先看下 acquireQueued 方法:
着重看我圈起来的那块。
我们都知道线程是可以被中断的,如果当前线程阻塞等待锁的过程被中断唤醒,此时如果前面有排队抢锁的线程,那么被中断的线程还是会因为抢不到锁而再次阻塞等待。
这样就无法响应这个中断了!
因此 acquireQueued 通过 interrupted 变量记录了之前产生过的中断,然后 acquire 方法判断,如果 interrupted = true 说明之前被中断过,则自己主动触发一次中断补上之前的中断!