【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;
}
相关文章
|
6天前
|
Java
java中的队列
这篇文章通过Java代码示例介绍了使用数组实现队列操作,包括队列的初始化、入队、出队、判断队列满和空以及遍历队列的方法。
java中的队列
|
10天前
|
Java 调度 开发者
Java并发编程:解锁多线程同步的奥秘
在Java的世界里,并发编程是提升应用性能的关键所在。本文将深入浅出地探讨Java中的并发工具和同步机制,带领读者从基础到进阶,逐步掌握多线程编程的核心技巧。通过实例演示,我们将一起探索如何在多线程环境下保持数据的一致性,以及如何有效利用线程池来管理资源。无论你是初学者还是有一定经验的开发者,这篇文章都将为你打开新的视野,让你对Java并发编程有更深入的理解和应用。
|
21天前
|
安全 Java 程序员
Java 并发编程:解锁多线程同步的奥秘
【7月更文挑战第30天】在Java的世界里,并发编程是一块充满挑战的领域。它如同一位严苛的导师,要求我们深入理解其运作机制,才能驾驭多线程的力量。本文将带你探索Java并发编程的核心概念,包括线程同步与通信、锁机制、以及并发集合的使用。我们将通过实例代码,揭示如何在多线程环境中保持数据的一致性和完整性,确保你的应用程序既高效又稳定。准备好了吗?让我们一同踏上这段解锁Java并发之谜的旅程。
26 5
|
23天前
|
安全 Java API
Java并发编程的艺术:解锁多线程同步与协作的秘密
【7月更文挑战第28天】在Java的世界中,并发编程如同一场精心编排的交响乐,每一个线程都是乐团中的乐手,而同步机制则是那指挥棒,确保旋律的和谐与统一。本文将深入探讨Java并发编程的核心概念,包括线程的创建、同步机制、以及线程间的通信方式,旨在帮助读者解锁Java多线程编程的秘密,提升程序的性能和响应性。
29 3
|
1月前
|
设计模式 安全 Java
Java面试题:请解释Java中的线程池以及为什么要使用线程池?请解释Java中的内存模型以及如何避免内存泄漏?请解释Java中的并发工具包以及如何实现一个简单的线程安全队列?
Java面试题:请解释Java中的线程池以及为什么要使用线程池?请解释Java中的内存模型以及如何避免内存泄漏?请解释Java中的并发工具包以及如何实现一个简单的线程安全队列?
33 1
|
1月前
|
前端开发 Java UED
Java中的同步与异步编程详解
Java中的同步与异步编程详解
|
1月前
|
设计模式 安全 NoSQL
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
40 0
|
1月前
|
存储 安全 Java
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
Java面试题:请解释Java内存模型,并说明如何在多线程环境下使用synchronized关键字实现同步,阐述ConcurrentHashMap与HashMap的区别,以及它如何在并发环境中提高性能
21 0
|
1月前
|
Java 数据安全/隐私保护
Java中的并发编程:锁与同步详解
Java中的并发编程:锁与同步详解
|
1月前
|
安全 NoSQL Java
网络安全-----Redis12的Java客户端----客户端对比12,Jedis介绍,使用简单安全性不足,lettuce(官方默认)是基于Netty,支持同步,异步和响应式,并且线程是安全的,支持R
网络安全-----Redis12的Java客户端----客户端对比12,Jedis介绍,使用简单安全性不足,lettuce(官方默认)是基于Netty,支持同步,异步和响应式,并且线程是安全的,支持R