【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,以下几个方面是必要的:

  1. 同步器:AQS 是同步器的一个抽象基类,通过继承 AQS 可以构建各种同步组件,如锁、信号量等。
  2. 状态:AQS 内部维护了一个状态变量,表示同步器的状态。同步器的具体含义由子类来定义。
  3. 队列:AQS 内部使用 FIFO 队列来存储等待获取同步状态的线程。当多个线程同时请求同步状态时,AQS 会将其中一个线程设置为独占模式,即该线程成为获取到同步状态的唯一持有者,其他线程则会被加入到等待队列中。
  4. 模板方法:AQS 采用了模板方法设计模式,在 AQS 中定义了一系列抽象方法和钩子方法,子类需要实现这些方法来定义自己的同步逻辑。
  5. CAS 和 volatile:AQS 内部使用了 CAS 和 volatile 等原语来保证同步器的正确性和并发性能。

总之,AQS 是 Java 中并发包中实现锁和其他同步器的基础框架,使用模板方法设计模式和 CAS 操作实现了高效、可扩展性高的同步器。理解 AQS 对于理解 Java 中并发编程的原理和实现非常重要。

AQS 如何实现

那接下来问一下 ChatGPT AQS 是如何实现地:

首先,AQS 的内部维护了一个 FIFO 的双向链表,用于存储等待获取锁的线程。当一个线程调用 acquire 方法时,如果当前没有其他线程持有锁,则直接获取锁;否则,将当前线程加入等待队列,并阻塞线程,直到获取到锁的时候再唤醒。

其次,AQS 还提供了一个 ConditionObject 类,用于实现线程的等待/通知机制。每个ConditionObject对象内部都维护了一个等待队列,用于存储等待条件满足的线程。当一个线程调用 await 方法时,将当前线程加入等待队列,并阻塞线程,直到条件满足的时候再唤醒;当一个线程调用 signal 方法时,将等待队列的第一个线程唤醒,使其从等待队列中移除,并加入到同步队列中等待获取锁。在使用 ConditionObject 时,需要先获取锁,才能调用 awaitsignal 方法。

最后,AQS还提供了一个 getStatesetState 方法,用于获取和设置当前同步状态。这个同步状态可以用于实现不同的同步语义,如读写锁中的读锁计数器。在实现自定义同步器时,可以使用这些方法来实现特定的同步语义。

需要注意的是,虽然 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 中 ReentrantLockCountDownLatchSemaphore 等同步器的实现。

#java##后端##java研发#
全部评论
看似火热,实则内卷
点赞 回复 分享
发布于 2023-03-28 17:27 重庆

相关推荐

牛客868257804号:九个中铁八个中建
点赞 评论 收藏
分享
2 6 评论
分享
牛客网
牛客企业服务