AQS是什么?

5 个回答

  • 铁血无双
    用眼睛是看不到事物的本质的,只有心灵才能够洞察一切。
    ### 什么是AQS? AQS全称为`AbstractQueuedSynchronizer`,翻译为抽象队列同步器。它是Java并发包`java.util.concurrent`的核心组件之一。如果说CAS(Compare-And-Swap)是Java并发编程的基础,那么AQS就是整个Java并发包的核心。 #### 主要特点 1. **双向队列** AQS内部维护一个双向队列,用于存放等待获取锁或其他同步状态的线程。每个等待的线程都会被封装成一个节点(Node)并加入这个队列。双向队列保证了FIFO(先进先出)的顺序,使得同步资源的获取和释放更加有序。 2. **核心方法** AQS定义了对双向队列的所有操作,而只开放了`tryAcquire`和`tryRelease`方法给开发者使用。开发者可以根据需要重写这两个方法,以实现自定义的同步功能。 3. **实现原理** 比如在`ReentrantLock`中,所有等待的线程都会被放在一个节点中并连接成双向队列。当前一个线程成功获取了锁,双向队列中的第一个节点就会被唤醒并开始运行。 ### AQS的核心方法 - **tryAcquire(int arg)**:尝试获取资源,具体实现由子类定义。 - **tryRelease(int arg)**:尝试释放资源,具体实现由子类定义。 - **acquire(int arg)**:获取资源,如果失败,将当前线程加入等待队列。 - **release(int arg)**:释放资源,并唤醒等待队列中的下一个线程。 - **acquireShared(int arg)**:共享模式下的获取资源。 - **releaseShared(int arg)**:共享模式下的释放资源。 ### 使用AQS的类 许多并发工具类都是基于AQS实现的,包括但不限于: - **ReentrantLock**:可重入锁 - **CountDownLatch**:倒计时锁存器 - **Semaphore**:信号量 - **ReadWriteLock**:读写锁 - **FutureTask**:用于异步计算的结果 ### 示例代码 以下是一个自定义同步器的简单示例,展示了如何扩展AQS来实现一个自定义锁: ```java 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 (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; } } public void lock() { sync.acquire(1); } public void unlock() { sync.release(1); } } public class SimpleLockTest { public static void main(String[] args) { SimpleLock lock = new SimpleLock(); lock.lock(); try { // critical section } finally { lock.unlock(); } } } ``` ### 总结 AQS(AbstractQueuedSynchronizer)是Java并发包的核心组件,通过维护一个双向队列来管理同步状态和线程的排队。它为实现高效的同步工具类提供了基础,开发者可以通过扩展AQS来创建自定义的同步器。
    发布于 2023-06-17 06:32 •IP属地 安徽
    举报
    评论
    收藏

  • 秦问天
    今朝有酒今朝醉,明日愁来明日愁。
    **AQS**(全称:**AbstractQueuedSynchronizer**)是 Java 并发包(`java.util.concurrent`)中的一个底层框架,专门用来实现**锁**和**同步器**。AQS 提供了一种实现**线程间同步**的通用机制,许多常用的同步工具(如 **ReentrantLock**、**Semaphore**、**CountDownLatch**)都是基于 AQS 实现的。 ### 1. **AQS 的核心概念** AQS 可以理解为一个**基础架构**,它通过一个**等待队列(FIFO 队列)**来管理获取锁或者同步状态的线程,保证线程按照顺序获取资源。 - **状态位(State)**:AQS 通过一个整数来表示当前的同步状态(类似锁的状态)。例如,0 表示没有线程持有锁,1 表示锁被某个线程持有。这个状态通常可以是任意数值,具体取决于实现的同步工具。 - **等待队列(CLH 队列)**:如果线程尝试获取锁时锁已经被其他线程占有,AQS 会将这个线程放到一个**等待队列**中,排队等待锁的释放。 - **独占模式和共享模式**:AQS 支持两种模式来控制锁的获取: 1. **独占模式**(Exclusive):一个线程独占资源,比如 `ReentrantLock`。只有一个线程能够获取锁,其他线程必须等待。 2. **共享模式**(Shared):多个线程可以共享资源,比如 `Semaphore` 或 `CountDownLatch`。多个线程可以同时获取同步状态,类似多个线程同时获取一个许可。 ### 2. **AQS 的工作原理** AQS 的核心是一个**双向链表**构成的**等待队列**,以及一个**状态位(state)**来表示锁的状态。工作流程如下: 1. **获取锁**: - 当一个线程尝试获取锁时,AQS 会检查当前锁的状态位 `state`。 - 如果 `state` 表示锁是可用的(比如 `state = 0`),线程会成功获取锁,并将 `state` 设置为 1。 - 如果锁已经被占用(`state = 1`),线程会被挂起,并加入 AQS 的**等待队列**中排队。 2. **等待队列**: - AQS 维护一个等待队列,所有未能成功获取锁的线程都会被加入到该队列中等待。 - 当锁被释放时,AQS 会通知队列中的下一个线程尝试获取锁。 3. **释放锁**: - 当持有锁的线程完成任务后,会释放锁(即将 `state` 重置为 0)。 - 然后 AQS 会唤醒等待队列中的下一个线程,该线程尝试重新获取锁。 ### 3. **AQS 的两种模式** #### 1. **独占模式**(Exclusive) 在独占模式下,**一个线程持有锁,其他线程必须等待**。这是最常见的场景,像 `ReentrantLock` 就是基于 AQS 的独占模式来实现的。 流程: - 当一个线程获取到锁后,`state` 从 0 变为 1。 - 其他尝试获取锁的线程将进入等待队列,直到持有锁的线程释放锁,`state` 变回 0,队列中的下一个线程被唤醒并获得锁。 **示例:** ```java ReentrantLock lock = new ReentrantLock(); lock.lock(); // 独占获取锁 try { // 执行任务 } finally { lock.unlock(); // 释放锁 } ``` #### 2. **共享模式**(Shared) 在共享模式下,**多个线程可以同时获取锁**。像 `Semaphore`(信号量)和 `CountDownLatch` 就是基于共享模式来实现的。可以允许多个线程同时获取资源。 流程: - 在共享模式下,多个线程可以共享 `state`,多个线程同时进入临界区执行任务,而不是像独占模式那样一次只允许一个线程。 **示例:** ```java Semaphore semaphore = new Semaphore(3); // 允许3个线程同时获取 semaphore.acquire(); // 获取一个许可 try { // 执行任务 } finally { semaphore.release(); // 释放一个许可 } ``` ### 4. **AQS 实现的工具类** **AQS** 是一个非常底层的框架,直接使用 AQS 会比较复杂,因此 Java 提供了很多基于 AQS 实现的常用同步工具: - **ReentrantLock**:独占模式的可重入锁。一个线程获取锁时,其他线程必须等待,类似于 `synchronized`。 - **Semaphore**:共享模式的信号量控制,允许多个线程同时执行任务,适用于资源有限的场景。 - **CountDownLatch**:允许一个或多个线程等待,直到其他线程执行完任务(共享模式)。 - **ReentrantReadWriteLock**:读写锁,允许多个线程同时读取,但只能一个线程写入。 这些工具的底层都依赖 AQS 提供的等待队列、状态位等机制来管理线程的并发操作。 ### 5. **AQS 的实现机制总结** - **状态管理**:AQS 通过 `state` 变量表示锁的状态。0 表示锁空闲,1 表示锁被占用,或者其他值用于控制复杂同步机制(如共享锁)。 - **等待队列**:通过 FIFO(先进先出)的队列来管理等待锁的线程。线程会根据锁的状态进入等待队列,等待锁的释放。 - **锁的获取和释放**:通过 `acquire` 和 `release` 方法来获取和释放锁。AQS 提供了这两个核心方法,子类可以根据具体的锁类型(独占或共享)来实现具体的逻辑。 ### 6. **通俗理解** 你可以把 AQS 想象成一个火车站的售票系统: - **state** 就是剩余的票数,如果 `state = 1`,就只有 1 张票,线程相当于乘客来抢这张票。拿到票的乘客上车,没拿到的乘客就排队。 - **等待队列** 就是乘客排队的队伍,所有想买票但当前买不到票的乘客都在这里等。 - **独占模式** 相当于只有一个座位的车厢,一次只能卖给一个乘客(ReentrantLock)。 - **共享模式** 则相当于车厢有多个座位,可以卖给多个乘客(Semaphore 或 CountDownLatch)。 AQS 帮助 Java 实现了这些复杂的并发控制机制,让我们可以通过简单的类(如 `ReentrantLock` 或 `Semaphore`)来完成复杂的同步操作,而不需要自己管理线程的状态和队列。 ### 总结 **AQS** 是一个用于构建锁和同步器的基础框架,它通过维护一个共享状态(`state`)和一个等待队列来管理并发访问的线程。它支持两种模式:**独占模式**和**共享模式**,能够灵活地用于实现多种并发控制工具,比如 `ReentrantLock`、`Semaphore` 和 `CountDownLatch` 等。 它的核心思想是:通过状态位 `state` 控制并发,使用等待队列管理那些没有获得锁的线程,确保线程按照顺序获取资源或执行任务。
    发布于 2023-03-30 15:33 •IP属地 山西
    举报
    评论
    收藏

  • 菜菜
    世界上有两个我,一个想去远方,一个身不由己。
    AQS全称是 AbstractQueuedSynchronizer ,形如其名,抽象队列同步器。 AQS定义了两种资源共享模式: 独占式,每次只能有一个线程持有锁,例如 ReentrantLock 实现的就是独占式的锁资源。
    发布于 2022-02-11 21:44 •IP属地 黑龙江
    举报
    评论
    收藏

  • 夜太
    明知不可为而为之就是我最大的诚意
    AQS就是AbstractQueuedSynchronizer,抽象队列同步器,是一个可以用于实现基于先进先出等待队列的锁和同步器的框架。实现锁 ReentrantLock,CountDownLatch,Semaphore,ReentrantReadWriteLock,SynchronousQueue,FutureTask等等皆是基于AQS的。 ReentrantLock其实就是有一个变量sync,Sync父类是AbstractQueuedSynchronizer ```java public class ReentrantLock implements Lock, java.io.Serializable { private final Sync sync; } ``` ReentrantLock的非公平锁与公平锁的区别在于非公平锁在CAS更新state失败后会调用tryAcquire()来判断是否需要进入同步队列,会再次判断state的值是否为0,为0会去CAS更新state值,更新成功就直接获得锁,否则就进入等待队列。(进等待队列之前会抢锁) 而公平锁首先判断state是否为0,为0并且等待队列为空,才会去使用CAS操作抢占锁,抢占成功就获得锁,没成功并且当前线程不是获得锁的线程,都会被加入到等待队列。
    发布于 2022-02-10 06:35 •IP属地 西藏
    举报
    评论
    收藏

  • 松林
    入体冰凉的月光,都比不上你绝情的目光。
    AQS是AbstractQueuedSynchronizer的缩写,是一个抽象同步队列类,可以基于它实现一个锁,例如ReentrantLock,只是需要实现tryAcquire()方法(也就是获取资源的方法,判断当前线程能否申请到独占锁)和tryRelease()方法(也就是释放资源的方法,在线程释放锁前对state进行更新),AQS会根据tryAcquire()的返回结果,来进行下一步的操作, 如果为true,代表线程获得锁了。 如果为false,代表线程没有获得锁,由AQS负责将线程添加到CLH等待队列中,并且进行阻塞等待。当前一个线程释放锁时,AQS对这个线程进行唤醒。 (不同的自定义同步器争用共享资源的方式也不同。自定义同步器在实现时只需要实现资源state的获取与释放方法即可,至于具体线程等待队列的维护(如获取资源失败入队/唤醒出队等),AQS已经在顶层实现好了) ### 公平锁加锁过程 加锁方法调用栈 ```java ReentrantLock lock = new ReentrantLock(); lock.lock();//在我们代码中通过调用lock方法进行加锁 FairSync.lock();//lock方法中会调用公平锁类FairSync的lock方法 AbstractQueuedSynchronizer.acquire(1);//FairSync继承于Sync,而Sync继承于AQS,FairSync的lock方法中调用了acquire方法也就是去申请独占锁资源。 public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); } FairSync.tryAcquire()//调用FairSync的tryAcquire去尝试着申请独占锁资源 AbstractQueuedSynchronizer.addWaiter()//申请失败就将线程添加到等待队列尾部 AbstractQueuedSynchronizer.acquireQueued()//并且让这个线程进入阻塞等待状态 ``` 对于加锁,ReentrantLock只需要去实现tryAcquire()方法,去根据state判断当前线程能不能获取锁,能获取就会返回true。不能获取就会返回false,然后由AQS来将未获得锁的线程添加到CLH队列尾部,然后等待被唤醒。(公平锁与非公平锁也就在于ReentrantLock的tryAcquire实现的区别,当锁被其他线程占用时,公平锁是只有当前等待队列没有其他线程时,才能去抢锁,而非公平锁则没有这个限制,在申请锁时就能去抢锁。) 可以看到AQS的acquire()方法中是会先去调用tryAcquire()去尝试着申请独占锁资源,AQS默认的tryAcquire()方法只有一行代码,会抛出UnsupportedOperationException异常(强制子类对这个方法进行实现)。所以ReentrantLock的FairSync对tryAcquire()方法进行了实现。 tryAcquire()返回true就代表获取独占锁资源成功: 1.等待队列没有其他线程且这个线程CAS操作设置state成功 2.当前线程就是持有锁的线程,那么只需要对state+1即可。 tryAcquire()返回false代表获取独占锁资源失败, 那么就调用AQS.addWaiter()方法申请失败就将线程添加到等待队列尾部,AQS.acquireQueued()方法让这个线程进入阻塞等待状态(在阻塞之前如果等待队列只有这一个线程,是会先尝试着获取锁,失败才会进入阻塞状态。) ```java //公平锁的tryAcquire方法实现 protected final boolean tryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { //等待队列中没有线程,使用cas操作去抢锁,抢锁成功,就返回true if (!hasQueuedPredecessors() && compareAndSetState(0, acquires)) { setExclusiveOwnerThread(current); return true; } } //当前线程与持有锁的线程是同一个,那么进行重入 else if (current == getExclusiveOwnerThread()) { int nextc = c + acquires; if (nextc < 0) throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; } ``` ### 公平锁解锁过程: 解锁方法调用栈 ```java ReentrantLock lock = new ReentrantLock(); lock.unlock(); AbstractQueuedSynchronizer.release(1); //AbstractQueuedSynchronizer的release()方法 public final boolean release(int arg) { if (tryRelease(arg)) { Node h = head; if (h != null && h.waitStatus != 0) unparkSuccessor(h); return true; } return false; } //ReentrantLock中Sync的tryRelease方法 protected final boolean tryRelease(int releases) { int c = getState() - releases; if (Thread.currentThread() != getExclusiveOwnerThread()) throw new IllegalMonitorStateException(); boolean free = false; //state为0就释放锁,否则只是锁的state减去releases if (c == 0) { free = true; setExclusiveOwnerThread(null); } setState(c); return free; } ``` 对于解锁,ReentrantLock的公平锁和非公平锁的实现是一样的,都是对state赋以最新的值,然后由AQS的unparkSuccessor方法负责对线程进行唤醒。 [https://www.cnblogs.com/waterystone/p/4920797.html](https://www.cnblogs.com/waterystone/p/4920797.html)
    发布于 2022-02-06 07:23 •IP属地 云南
    举报
    评论
    收藏