写点什么

【JAVA】浅说 AQS

作者:sidiot
  • 2023-06-25
    浙江
  • 本文字数:2386 字

    阅读完需:约 8 分钟

前言


又迎来了一年一度的金三银四,虽然说今年的大环境不好,但是招聘还是在火热进行中。


面试过 Java 工程师的小伙伴都知道,Java 中的 AQS 是面试高频题,面试官上来就直接了当地问,AQS 知道是什么吧,来讲讲它是怎么实现的,以及哪些地方用到了它。


那么接下来,让我们来了一下 AQS 的相关内容。


什么是 AQS


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 如何实现


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


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


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


需要注意的是,虽然 AQS 提供了强大的同步器框架,但是自定义同步器需要非常小心,以避免出现死锁、饥饿等问题。在实现自定义同步器时,需要仔细分析应用场景,理清线程之间的依赖关系,以确保同步器的正确性和高效性。


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 等同步器的实现。


后记


以上就是 浅说 AQS 的所有内容了,希望本篇博文对大家有所帮助!

发布于: 2023-06-25阅读数: 30
用户头像

sidiot

关注

还未添加个人签名 2023-06-04 加入

还未添加个人简介

评论

发布
暂无评论
【JAVA】浅说 AQS_Java_sidiot_InfoQ写作社区