【JAVA】让 ChatGPT 来浅说 AQS

发布于:2023-03-28 ⋅ 阅读:(353) ⋅ 点赞:(0)

前言
又迎来了一年一度的金三银四,虽然说今年的大环境不好,但是招聘还是在火热进行中。
面试过 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 等同步器的实现。
后记
以上就是 让 ChatGPT 来浅说 AQS 的所有内容了,希望本篇博文对大家有所帮助!

本文含有隐藏内容,请 开通VIP 后查看