关于AQS的一点总结

限时加码!20+主流AI编程工具免费用 购周边加赠Coding Plan Lite,Claude Code、Cursor等即刻畅享,学习进阶更高效! 阅读详情

为什么标题叫做对于AQS的一点理解呢,因为感觉自己还没有达到能够对AQS理解透彻,哈哈。这段时间在总结以前学过的东西,发现大牛写的东西真的是厉害,越学越谦虚,越学越感觉自己还有好多好多的东西要学。大牛写的东西不仅体现在结构、架构上,而且更令人惊叹的是在细微之处。比如,HashMap的数组长度为什么是2的整数次幂;CopyOnWriteArrayList的set方法什么都没做的时候还要重新进行赋值(详情请看CopyOnWriteArrayList类set方法疑惑?);JDK1.8中HashMap和ConcurrentHashMap的扩容操作,等等。这些细节才是体现水平的地方,我只有对大牛仰慕的份。

好吧,回到正题。
正如标题,这只是部分总结,我尝试用自己语言将其表达出来,目的是理清AQS的一些关键脉络,比如:工作原理、内部结构、线程的处理等等;
而没有对AbstractQueuedSynchronizer就行全面介绍。

如果您之前还没有接触到AQS,那么本篇博客可能对您帮助不大;
如果您也是跟我一样,已经了解过AQS想总结一下,那么我们可能会产生共鸣;
如果您之前已经对AQS非常熟悉了,如果有不对的地方希望您不吝赐教,谢谢~

一句话概括

如果要我对AQS用一句话进行概括,我会这样概括:

AQS利用CAS原子操作维护自身的状态,结合LockSupport对线程进行阻塞和唤醒从而实现更为灵活的同步操作。

两个关键点

上面的一句话提炼一下,我其实想表达两个点:

  1. 通过CAS操作维护自身的状态
  2. 一个就是如何对线程的进行处理

CAS维护自身状态

CAS

CAS,CompareAndSwap,比较并交换。是一种乐观锁的思想,不对资源进行加锁而是与原来的值进行比较并交换:如果相等,意味着这个值没有被更新过,可以进行操作;否则说明该值已经被其它线程更改过了,那么需要进行重试。
因为不需要阻塞,也就没有了线程切换的开销,所以这种方法能够取得比较好的效率;

CAS需要三个参数:

  • offset(内存地址)
  • expect(期望值)
  • update(需要更新的值)

操作成功返回true,否则返回false。

维护什么状态

AQS内部的属性并不是很多,我们可以找到有这么一个属性:

/**
 * The synchronization state.
 */
private volatile int state;

这个变量,我觉得是AQS非常核心的一个变量。并发包中的工具类都是通过继承AQS暴露的方法(protected)来操作这个变量,从来实现各种各样有的同步功能;

怎么维护?

这里涉及到了一个设计模式-模版方法,子类通过实现protected方法,在protected方法里面实现自己的逻辑从而影响整个流程以及对status变量的操作。

也就是说子类所有的同步功能都是建立在这个status变量上的。

这样说起来有点抽象,考虑到可阅读性以及博客的重心是在AQS上,所以我后面把concurrent包下的同步器单独写一篇博客介绍,希望大家继续关注 ^_^

LockSupport对线程的处理

如标题所示,AQS对线程的处理主要是依赖LockSupport的功能

阻塞与唤醒

最开始接触AQS的时候,我最好奇的也是这个地方:AQS不是JVM直接支持的一个关键字,不像synchronized关键字可以得到JVM的支持;而AQS只是一个普通的Java类,那么它如何对线程进行阻塞和唤醒的呢?

其实很简单(只是思路很简单,实现起来可就不简单了,哈哈)。
我们来看一个源码:


public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            //调用unparkSuccessor()唤醒线程
            unparkSuccessor(h);
        return true;
    }
    return false;
}

//来看upparkSuccessor()方法
private void unparkSuccessor(Node node) {

    //省略无关代码
    LockSupport.unpark(s.thread);
}

public static void unpark(Thread thread) {
    //省略无关代码
    if (thread != null)
        //委托给UNSAFE#unpark方法,这是一个本地方法
        UNSAFE.unpark(thread);
}

从调用链中我们可以了解到线程的唤醒操作是委托UNSAFE#unpark方法的,而这是一个本地方法,也就是说这个唤醒操作实际上是通过操作系统完成的。
同理,我们可以推导出阻塞的实现是通过UNSAFE#park方法方法实现的。

Node与CLH队列

从doc中,我们可以找到这么一幅图,它描述了内部的队列是如何构成的(我很好奇为什么只画了prev指针,为什么没有next指针,是不是因为地方太小画不下了,哈哈);


     +------+  prev +-----+       +-----+
head |      | <---- |     | <---- |     |  tail
     +------+       +-----+       +-----+
Node

Node,队列节点,每一个Node都持有了一个线程,对线程进行包装,方便操作。

我们需要特别关注一下其内部的五个属性:


static final class Node{

    /*当前node对象的等待状态,注意该状态并不是描述当前对象而是描述下一个节点的状态,
     * 从而来决定是否唤醒下一个节点,该节点总共有四个取值:
     * a. CANCELLED = 1:因为超时或者中断,结点会被设置为取消状态,被取消状态的结点不应该去竞争锁,
     * 只能保持取消状态不变,不能转换为其他状态。处于这种状态的结点会被踢出队列,被GC回收;
     * b. SIGNAL = -1:表示这个结点的继任结点被阻塞了,到时需要通知它; 
     * c. CONDITION = -2:表示这个结点在条件队列中,因为等待某个条件而被阻塞;
     * d. PROPAGATE = -3:使用在共享模式头结点有可能牌处于这种状态,表示锁的下一次获取可以无条件传播;
     * e. 0: None of the above,新结点会处于这种状态。
     * 
     * 非负值标识节点不需要被通知(唤醒)。
    */
    volatile int waitStatus;

    //当前节点的上一个节点,如果是头节点那么值为null
    volatile Node prev;
    //当前节点的下一个节点
    volatile Node next;

    //与Node绑定的线程对象
    volatile Thread thread;

    //下一个等待条件(Condition)的节点,由于Condition是独占模式,因此这里有一个简单的队列来描述Condition上的线程节点。
    Node nextWaiter;

}

当线程中的对象调用AQS子类的方法尝试更改AQS维护的状态失败时,就会将Thread对象抽象成这样的Node对象,这样更加利于管理。

Node中状态的作用:
当头节点也就是当前线程运行完毕以后,会检查自身的waitStatus,从而决定是否将后继节点唤醒。

为什么是通过检查头节点的waitStatus来决定唤醒操作,而不是检查其它节点的waitStatus?请参看请参看这篇文章-JAVA并发编程学习笔记之CLH队列锁

CLH队列

CLH队列是什么鬼?请参看这篇文章-JAVA并发编程学习笔记之CLH队列锁

上面说到当线程尝试更改AQS状态操作获得失败时,会将Thread对象抽象成Node对象,但是这些Node对象在AQS内部是如何内部管理的呢?
AQS会将Node对象加入到内部维护的一个的队列中,从上面Node的属性中我们可以得出这个队列是一个双向的链表结构。既然是链表,那么就会遵循先进先出(FIFO)规则,所以也就解决了线程排队的问题。

既然解决了排队问题,那么这个队列又是如何运作的呢?
因为遵循先进先出规则,所以我们不难想到队列的头节点应该是表示当前正在运行的线程;当头节点运行完毕,会检查自身的状态waitStatus来决定是否唤醒下一个节点。如果需要对下一个节点进行唤醒,那么会通过CAS操作将下一个节点设置成头节点;否则从队尾开始往前找,直到找到最靠前的需要唤醒的节点。


/**
 * Wakes up node's successor, if one exists.
 *
 * @param node the node
 */
private void unparkSuccessor(Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     * 
     * 如果waitStatus小于0,那么将下一个节点设置成头节点
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     * 
     * 如果waitStatus大于0,或者下一个节点为null,那么从后往前找
     * 之前与同学讨论的时候不是很明白为什么要从后往前找,现在看了下doc瞬间明白了:
     * 下一个节点有可能因为任务被取消了,节点有可能变为null
     */
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

总结

这篇文章没有详细说明关于AQS的所有信息,而是尝试从这两个角度去理清AQS的脉络:
1. CAS操作自身状态;
2. 如何处理线程;

然后展开的说明了CAS、自身状态的作用、如何操作、以及对线程的处理、阻塞时怎么办、如何唤醒等等;

抓住了运行原理,那么理解AQS就比较简单了;

限于篇幅以及文章的侧重点,所以没有叙述concurrent包中的同步器是如何使用AQS的,后面我会把这部分内容补上~

参考资料

我不能保证写的东西完全正确
但是我能保证我写的东西都是经过思考之后所得出,而不是复制粘贴得来
如果有不对的地方,希望您能够指正,谢谢~

(面经总结)一篇文章带你完整复习 Java 中的 AQS 文章目录一、什么是AQS二、AQS的原理三、state:状态四、AQS共享资源的方式:独占式和共享式 一、什么是AQS AQS(Abstract Queued Synchronizer)是一个抽象的队列同步器,通过维护一个共享资源状态(Volatile Int State)和一个先进先出(FIFO)的线程等待队列来实现一个多线程访问共享资源的同步框架。 二、AQS的原理 AQS为每个共享资源都设置一个共享资源锁,线程在需要访问共享资源时首先需要获取共享资源锁,如果获取到了共享资源锁,便可以在当前线程中使用该 阅读详情

相关推荐

命令行驱动视频剪辑:cutcli与AI自动化工作流实战

在视频内容创作领域,自动化与批量化处理是提升生产效率的关键。传统图形界面工具在应对重复性任务时往往效率低下,而通过命令行接口(CLI)驱动视频生成,则能实现流程的程序化控制。其技术原理在于将视频结构抽象为可编程的指令序列,通过解析命令生成标准化的工程文件。这种模式的核心价值在于将视频制作从手动操作转变为代码驱动,特别适合与AI编程助手集成,实现自然语言到视频草稿的自动转换。应用场景广泛覆盖社交媒体内容批量生产、教育视频自动化生成、数据可视化视频组装等领域。本文以cutcli工具为例,深入解析如何通过命令行与

weixin_42520239的博客 538

AQS简单介绍与使用

AQS,全称是 AbstractQueuedSynchronizer,中文译为抽象队列式同步器。这个抽象类对于JUC并发包非常重要,JUC包中的ReentrantLock,,Semaphore,ReentrantReadWriteLock,CountDownLatch 等等几乎所有的类都是基于AQS实现的。 AQS 中有两个重要的东西,一个等待队列,还有一个STATE标志,并且通过CAS来改变它的...

charming的专栏 2万+

一文读懂GPU通信互联技术:GPUDirect、NVLink与RDMA

GPUDirect是NVIDIA开发的一项技术,旨在实现GPU与其他设备(如网络接口卡NIC和存储设备)之间的直接通信和数据传输,而无需CPU的参与。传统上,数据在GPU和另一个设备之间传输时,必须通过CPU,这导致潜在的性能瓶颈和延迟增加。GPUDirect技术则通过绕过CPU,直接访问和传输数据,显著提高系统性能。NVLink是NVIDIA开发的一种高速、高带宽的互连技术,用于连接多个GPU之间或GPU与其他设备(如CPU、内存等)之间的通信。

异构算力老群群(在读985计算机博士生)的技术博客 7667

吐血总结:AQS到底是什么?

文章目录1、概述2、基本框架2.1、AQS框架介绍2.2、AQS核心成员变量和方法3、源码分析3.1、CLH队列(FIFO)3.2、独占模式获取资源3.2.1、acquire(int)3.2.1.1、tryAcquire(int arg)3.2.1.2、addWaiter(Node mode)3.2.1.3、acquireQueued(final Node node, int arg)3.2.1.3.1、shouldParkAfterFailedAcquire(p, node)3.2.1.3.2、parkA

u011047968的专栏 7022

AQS 工作原理

而且tryRelease()的返回值代表着该线程是否已经完成资源的释放,因此在自定义同步器的tryRelease()时,需要明确这条件,当已经彻底释放资源(state=0),要返回true,否则返回false。unparkSuccessor用unpark()唤醒等待队列中最前驱的那个未放弃线程,此线程并不一定是当前节点的next节点,而是下一个可以用来唤醒的线程,如果这个节点存在,调用unpark()方法唤醒。如果获取到资源,线程直接返回,否则进入等待队列,直到获取到资源为止,且整个过程忽略中断的影响。

worn_xiao的博客 8977

AQS详解

AQS详解

weixin_45822634的博客 4023

JAVA并发之对AQS一点理解

JAVA并发之谈谈你对AQS的理解

qq_39939541的博客 955

AQS一点点的学习

  private Node enq(final Node node) { for (;;) { Node t = tail; if (t == null) { // Must initialize if (compareAndSetHead(new Node())) ...

zhouwei54321的博客 185

关于AQS一点体会 一独占模式

我们都知道在Java并发场景下JUC提供很多工具类,但是AbstrctQueuedSynchronizer这个抽象同步队列绝对是核心和基石。在理解这个框架运行机制中,很多抽象的逻辑让我们理解起来很困难,所以想分享一些自己在理解过程的心得,跟大家交流探讨,有不对的地方望各位大神指正,谢谢! 实现独占模式的工具类有ReentrantLock,无非就是重写了tryAcquire() 和tryRelease()方法,以及公平与不公平获得锁机制的分别实现,ReentrantLock默认实现非...

m0_56091122的博客 808

Java并发基础之对于CAS和AQS原理的一点理解

CAS(Compare And Swap) CAS可以简单的理解为比较并替换,利用CPU的CAS指令,同时借助JNI(Java Native Interface为JAVA本地调用)来完成Java的非阻塞算法,实现原子操作。 CAS机制中,使用了3个参数,当前状态下内存中的数值:a;内存中原值:b(该值不是当前状态下内存中的值,而实在操作开始时,从内存中读取的值);要修改的新值:c。在更新一个变量的...

TCJGGSDDU的博客 1239

Java中的AQSJava6和Java7中的一点小不同

最近在学习xylz博客中关于Java Concurrency部分。看到了其中的AQS部分,就在Eclipse中查看了这个类的源码。正好我同时装了Jdk6和Jdk7,所以想看看在Jdk7中有没有进行改变。在看到ReentrantLock.FairSync.tryAcquire是发现

rongruo_lsx的专栏 1649

什么是AQS及其原理

GitHub:https://github.com/JDawnF 1、AQS简介 AQS全名:AbstractQueuedSynchronizer,是并发容器J.U.C(java.lang.concurrent)下locks包内的一个类。它实现了一个FIFO(FirstIn、FisrtOut先进先出)的队列。底层实现的数据结构是一个双向链表。 Sync queue:同步队列,是一个双向链...

striveb的博客 4万+

什么是AQS

AQS ( Abstract Queued Synchronizer )是一个抽象的队列同步器,通过维护一个共享资源状态( Volatile Int State )和一个先进先出( FIFO )的线程等待队列来实现一个多线程访问共享资源的同步框架。

qq_40322236的博客 2万+

AQS原理详解

1.AQS 原理概览 AQS核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。 CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点

a984171281的博客 2万+

AQS是什么

而且tryRelease()的返回值代表着该线程是否已经完成资源的释放,因此在自定义同步器的tryRelease()时,需要明确这条件,当已经彻底释放资源(state=0),要返回true,否则返回false。unparkSuccessor用unpark()唤醒等待队列中最前驱的那个未放弃线程,此线程并不一定是当前节点的next节点,而是下一个可以用来唤醒的线程,如果这个节点存在,调用unpark()方法唤醒。当该节点获取到同步状态时,它会清除自己的值,将自己作为head节点,以便唤醒下一个节点。

weixin_50687701的博客 1890

Java并发——AQSAQS到底什么是AQS?这玩意干啥的?

大白话 简单讲就是两句话: 1、是什么? AQS就是一个工具类,算是在并发情况下规范了对“资源”或者说“数据”操作的一种机制或者说是规范。专业一点的叫法是:抽象队列同步器 2、这玩意干啥呢? AQS是给JAVA一系列锁以及同步器或者同步对象的底层提供了实现的框架,你也可以理解成为一种规范。就比如实现像ReentrantLock,CountDownLatch,Semaphore这样的工具。 来提供一下网上比较多的两句话: AQS是什么:AQS,是AbstractQueuedSynchronizer简称,直翻过

泯灭于众生,高歌于孤夜! 4297

AQS源码导读

我想挑战一下我的软肋:AQS源码导读。

程序员小潘 9927

AQS实现原理

AQS中维护了一个volatile int state(共享资源)和一个CLH队列。当state=1时代表当前对象锁已经被占用,其他线程来加锁时则会失败,失败的线程被放入一个FIFO的等待队列中,然后会被UNSAFE.park()操作挂起,等待已经获得锁的线程释放锁才能被唤醒。 我们拿具体场景来分析,假设同时有三个线程并发抢占锁,此时线程一抢占成功,线程二、三抢占失败,具体流程如下: 此时AQS内部数据结构为: 上图可以看到等待队列中的节点Node是一个双向链表,这里SIGNAL是No..

summer_fish的专栏 5525
上一篇: 如何创建一个不被回收的对象
下一篇: 从斐波那契数列说起
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值