【Java基础】AQS (AbstractQueuedSynchronizer) 抽象队列同步器

简介: AQS 是一个相对底层的同步器框架,对于一些常见的同步需求,Java 并发库已经提供了许多高级封装,如 ReentrantLock、ReadWriteLock、Semaphore 等,这些高级封装已经为我们提供了更简单易用的接口和功能。因此,在应用开发中,直接使用 AQS 的场景相对较少,更多的是通过使用它的子类来实现具体的同步机制。
关于作者:CSDN内容合伙人、技术专家, 从零开始做日活千万级APP。
专注于分享各领域原创系列文章 ,擅长java后端、移动开发、人工智能等,希望大家多多支持。

一、导读

我们继续总结学习Java基础知识,温故知新。

1.1 CLH锁

CLH(Craig, Landin, and Hagersten locks)是一种自旋锁,能确保无饥饿性,提供先来先服务的公平性。
CLH锁是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程只在本地变量上自旋,它不断轮询前驱的状态,如果发现前驱释放了锁就结束自旋。
在这里插入图片描述

二、概览

AbstractQueuedSynchronizer 是抽象队列同步器,是一种用来构建锁和同步器的框架

AQS主要做了三件事情

  • 同步状态的管理
  • 线程的阻塞和唤醒
  • 同步队列的维护

AQS 定义了同步器的基本操作,如获取、释放和状态管理,并提供了一个等待队列来管理等待资源的线程,解决了在实现同步器时涉及的大量细节问题,例如自定义标准同步状态、FIFO 同步队列。

基于 AQS 来构建同步器可以带来很多好处。它不仅能够极大地减少实现工作,而且也不必处理在多个位置上发生的竞争问题。

三、使用场景

AQS 是一个相对底层的同步器框架,对于一些常见的同步需求,Java 并发库已经提供了许多高级封装,如 ReentrantLock、ReadWriteLock、Semaphore 等,这些高级封装已经为我们提供了更简单易用的接口和功能。因此,在应用开发中,直接使用 AQS 的场景相对较少,更多的是通过使用它的子类来实现具体的同步机制。

常用的同步器有:

  1. 独占锁(如 ReentrantLock):AQS 提供了 acquire(int arg) 和 release(int arg) 等方法,开发人员可以继承 AQS 并实现自定义的同步器来实现独占锁。通过控制同步状态(通过 getState() 和 setState(int newState) 方法),以及管理等待线程(通过等待队列),AQS 可以提供可重入锁、公平锁等不同类型的独占锁。
  2. 共享锁(如 ReadWriteLock):AQS 也可以用于实现共享锁机制,例如 ReentrantReadWriteLock。通过 acquireShared(int arg) 和 releaseShared(int arg) 等方法,开发人员可以自定义实现共享锁的逻辑。AQS 提供了对多个读线程和写线程的管理和协调,以及对读线程的优化。
  3. 实现其他同步工具:AQS 的框架还可以用于实现其他类似的同步工具,如信号量(Semaphore)、倒计时器(CountDownLatch)、循环屏障(CyclicBarrier)等。

通过继承 AQS 并自定义同步器的行为,可以实现不同的同步机制。

3.1 AQS 对资源的共享方式

  1. Exclusive(独占):只有一个线程能执行,如ReentrantLock。

    资源锁可分为公平锁和非公平锁:

  • 公平锁:按照线程在队列中的排队顺序(FIFO),先到者先拿到锁。

在这里插入图片描述

  • 非公平锁:当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的(被唤醒的线程和新来的线程重新竞争锁)。

在这里插入图片描述

  1. Share(共享):多个线程可同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock 我们都会在后面讲到。

四、原理

AQS大致流程如下:
1、当某一线程获取锁后,将state值+1,并记录下当前持有锁的线程。
2、再有线程来获取锁时,判断这个线程与持有锁的线程是否是同一个线程,如果是,将state值再+1,如果不是,阻塞线程(调用 LockSupport.park(this)挂起线程)。
3、当线程释放锁时,将state值-1。
4、当state值减为0时,表示当前线程彻底释放了锁。
5、然后将记录当前持有锁的线程的那个字段设置为null,并唤醒其他线程,使其重新竞争锁

4.1 原理

AQS使用一个 Volatile的 int类型的成员 state 变量来表示同步状态,通过内置的FIFO队列来完成资源获取的排队工作(双向链表,多线程争用资源被阻塞时会进入此队列),然后通过CAS完成对State值的修改。

其并发控制的核心是锁的获取与释放,锁的实现方式有很多种,AQS采用的是一种改进的CLH锁。


    当state=0表示释放了锁,当state>0表示获得锁
    /**
     * The synchronization state.
     */
    private volatile int state;
    
    
    封装一个Node,包含前节点,后节点,组成一个双向队列。
    private transient volatile Node head;

    private transient volatile Node tail;

在这里插入图片描述

CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。

AQS是将每条请求共享资源的线程封装成一个CLH锁队列(FIFO同步队列)的一个结点(Node)来实现锁的分配。

如果线程获取当前同步状态失败,AQS会将当前线程的信息封装成一个Node节点,加入同步队列中,并且阻塞该线程,当同步状态释放,则会将队列中的线程唤醒,重新尝试获取同步状态。

static final class Node {
    /** 共享节点 */
    static final Node SHARED = new Node();
    /** 独占节点 */
    static final Node EXCLUSIVE = null;

    当前节点在队列中的状态
    volatile int waitStatus;

    前驱指针
    volatile Node prev;

    后继指针
    volatile Node next;

    表示处于该节点的线程
    volatile Thread thread;

    指向下一个处于CONDITION状态的节点
    Node nextWaiter;
}
相关文章
|
8月前
|
设计模式 人工智能 安全
AQS:Java 中悲观锁的底层实现机制
AQS(AbstractQueuedSynchronizer)是Java并发包中实现同步组件的基础工具,支持锁(如ReentrantLock、ReadWriteLock)和线程同步工具类(如CountDownLatch、Semaphore)等。Doug Lea设计AQS旨在抽象基础同步操作,简化同步组件构建。 使用AQS需实现`tryAcquire(int arg)`和`tryRelease(int arg)`方法以获取和释放资源,共享模式还需实现`tryAcquireShared(int arg)`和`tryReleaseShared(int arg)`。
451 32
AQS:Java 中悲观锁的底层实现机制
|
9月前
|
前端开发 Java
java实现队列数据结构代码详解
本文详细解析了Java中队列数据结构的实现,包括队列的基本概念、应用场景及代码实现。队列是一种遵循“先进先出”原则的线性结构,支持在队尾插入和队头删除操作。文章介绍了顺序队列与链式队列,并重点分析了循环队列的实现方式以解决溢出问题。通过具体代码示例(如`enqueue`入队和`dequeue`出队),展示了队列的操作逻辑,帮助读者深入理解其工作机制。
302 1
|
9月前
|
Java
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
前言 主播觉得,AQS的原理,就是通过这2个队列的协助,实现核心功能,同步队列(CLH队列)和条件队列(Condition队列)。 同步队列(CLH队列) 作用:管理需要获...
190 18
【源码】【Java并发】【AQS】从ReentrantLock、Semaphore、CutDownLunch、CyclicBarrier看AQS源码
|
10月前
|
设计模式 存储 安全
【Java并发】【AQS】适合初学者体质的AQS入门
AQS这是灰常重要的哈,很多JUC下的框架的核心,那都是我们的AQS,所以这里,我们直接开始先研究AQS。 那说到研究AQS,那我们应该,使用开始说起🤓 入门 什么是AQS? AQS(Abst
218 8
【Java并发】【AQS】适合初学者体质的AQS入门
|
存储 监控 Java
JAVA线程池有哪些队列? 以及它们的适用场景案例
不同的线程池队列有着各自的特点和适用场景,在实际使用线程池时,需要根据具体的业务需求、系统资源状况以及对任务执行顺序、响应时间等方面的要求,合理选择相应的队列来构建线程池,以实现高效的任务处理。
682 12
|
存储 算法 Java
【用Java学习数据结构系列】用堆实现优先级队列
【用Java学习数据结构系列】用堆实现优先级队列
207 0
|
存储 安全 Java
【用Java学习数据结构系列】探索栈和队列的无尽秘密
【用Java学习数据结构系列】探索栈和队列的无尽秘密
149 2
|
安全 Java 容器
Java并发编程 - 线程不安全类 & 同步/并发容器之简介
Java并发编程 - 线程不安全类 & 同步/并发容器之简介
210 0
Java并发编程 - 线程不安全类 & 同步/并发容器之简介
|
安全 Java 容器
java并发编程笔记3-同步容器&并发容器&闭锁&栅栏&信号量
一.同步容器:   1.Vector容器实现了List接口,Vector实际上就是一个数组,和ArrayList类似,但是Vector中的方法都是synchronized方法,即进行了同步措施。保证了线程安全。
1660 0