【JAVA】让 ChatGPT 来浅说 AQS
前言
又迎来了一年一度的金三银四,虽然说今年的大环境不好,但是招聘还是在火热进行中。
面试过 Java 工程师的小伙伴都知道,Java 中的 AQS 是面试高频题,面试官上来就直接了当地问,AQS 知道是什么吧,来讲讲它是怎么实现的,以及哪些地方用到了它。
那么接下来,让我们使用 ChatGPT 并结合自己的理解来讲述一下 AQS 的相关内容。
什么是 AQS
当博主提问 ChatGPT 什么是 AQS 时,ChatGPT 给出了如下回答:
大致意思就是说,AQS 全称 AbstractQueuedSynchronizer,是 Java 中并发包中用于实现锁和其他同步器的基础框架。它使用了 CAS 操作和 unsafe 类来实现同步器的状态更新以及线程的挂起和唤醒等操作,提供了一种通用的、高效且可扩展的同步机制,可以用来构建各种同步组件。
AQS 内部维护了一个 FIFO 队列,用于存储等待获取同步状态的线程。同时定义了一些模板方法,这些方法被子类实现,用于实现不同类型的同步器,例如 ReentrantLock、CountDownLatch、Semaphore 等。
AQS 使用了一种独特的模板方法设计模式,使用内部状态(一个 volatile
修饰的 state
变量)来控制同步器的行为,子类通过实现模板方法来控制同步器的状态变化。AQS 的内部状态可以被子类用于实现独占式、共享式的同步器。
综上,想要理解 AQS,以下几个方面是必要的:
- 同步器:AQS 是同步器的一个抽象基类,通过继承 AQS 可以构建各种同步组件,如锁、信号量等。
- 状态:AQS 内部维护了一个状态变量,表示同步器的状态。同步器的具体含义由子类来定义。
- 队列:AQS 内部使用 FIFO 队列来存储等待获取同步状态的线程。当多个线程同时请求同步状态时,AQS 会将其中一个线程设置为独占模式,即该线程成为获取到同步状态的唯一持有者,其他线程则会被加入到等待队列中。
- 模板方法:AQS 采用了模板方法设计模式,在 AQS 中定义了一系列抽象方法和钩子方法,子类需要实现这些方法来定义自己的同步逻辑。
- CAS 和 volatile:AQS 内部使用了 CAS 和 volatile 等原语来保证同步器的正确性和并发性能。
总之,AQS 是 Java 中并发包中实现锁和其他同步器的基础框架,使用模板方法设计模式和 CAS 操作实现了高效、可扩展性高的同步器。理解 AQS 对于理解 Java 中并发编程的原理和实现非常重要。
AQS 如何实现
那接下来问一下 ChatGPT AQS 是如何实现地:
首先,AQS 的内部维护了一个 FIFO 的双向链表,用于存储等待获取锁的线程。当一个线程调用 acquire
方法时,如果当前没有其他线程持有锁,则直接获取锁;否则,将当前线程加入等待队列,并阻塞线程,直到获取到锁的时候再唤醒。
其次,AQS 还提供了一个 ConditionObject
类,用于实现线程的等待/通知机制。每个ConditionObject
对象内部都维护了一个等待队列,用于存储等待条件满足的线程。当一个线程调用 await
方法时,将当前线程加入等待队列,并阻塞线程,直到条件满足的时候再唤醒;当一个线程调用 signal
方法时,将等待队列的第一个线程唤醒,使其从等待队列中移除,并加入到同步队列中等待获取锁。在使用 ConditionObject
时,需要先获取锁,才能调用 await
、signal
方法。
最后,AQS还提供了一个 getState
和 setState
方法,用于获取和设置当前同步状态。这个同步状态可以用于实现不同的同步语义,如读写锁中的读锁计数器。在实现自定义同步器时,可以使用这些方法来实现特定的同步语义。
需要注意的是,虽然 AQS 提供了强大的同步器框架,但是自定义同步器需要非常小心,以避免出现死锁、饥饿等问题。在实现自定义同步器时,需要仔细分析应用场景,理清线程之间的依赖关系,以确保同步器的正确性和高效性。
AQS 实现不可重入锁
上述光说理论可能不易于理解,因此让 ChatGPT 生成一个与 AQS 相关的代码示例:
相关代码如下:
import java.util.concurrent.locks.AbstractQueuedSynchronizer; public class SimpleLock { private final Sync sync = new Sync(); private static class Sync extends AbstractQueuedSynchronizer { @Override protected boolean tryAcquire(int arg) { if (getState() == 0 && compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } @Override protected boolean tryRelease(int arg) { if (getState() == 0) { throw new IllegalMonitorStateException(); } setExclusiveOwnerThread(null); setState(0); return true; } @Override protected boolean isHeldExclusively() { return getState() == 1; } } public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } } 复制代码
在上述示例代码中,我们首先定义了一个继承自 AbstractQueuedSynchronizer
的内部类 Sync
,用于实现不可重入锁。
然后,我们在 tryAcquire
方法中尝试获取锁,如果当前状态为0,且能够使用 CAS 操作将状态修改为1,表示成功获取到锁,否则获取锁失败。
在 tryRelease
方法中,我们释放锁,首先检查当前状态是否为0,如果是0,表示当前没有线程持有锁,抛出非法监视器状态异常,否则,使用 CAS 操作将状态修改为0,并将持有锁的线程设置为 null。
在 isHeldExclusively
方法中,我们判断当前是否有线程持有锁,如果状态为1,表示有线程持有锁,返回 true,否则返回 false。
然后,我们定义一个 SimpleLock
类,使用 Sync
内部类实现不可重入锁。在 lock
方法中,我们调用 acquire
方法来获取锁;在 unlock
方法中,我们调用 release
方法来释放锁。
设计一个测试用例,发现正如我们所预料的那样,获取锁与释放锁的功能正常,且当对象有锁之后,不能再获取到该对象了,即不可重入:
上述示例代码只是 AQS 的一个非常简单的应用,更复杂的应用可以参考 Java 中 ReentrantLock
、CountDownLatch
、Semaphore
等同步器的实现。