AQS 原理
AQS(AbstractQueuedSynchronizer)是 Java 中用于构建锁和同步器的框架,它是并发包中很多同步类的基础。AQS 提供了一种基于 FIFO 等待队列的同步器实现,通过内置的队列和状态管理机制,可以实现各种同步器,如 ReentrantLock、CountDownLatch、Semaphore 等。
AQS 的核心思想是使用一个整型的状态变量来表示同步状态,同时维护一个 FIFO 的等待队列来管理获取锁失败的线程。AQS 包含两种操作:独占操作(acquire)和共享操作(release)。独占操作是指只有一个线程可以获取同步状态,而共享操作是指多个线程可以同时获取同步状态。
AQS 的主要方法包括:
- acquire(int arg):获取同步状态,如果获取成功返回 true,否则阻塞等待。
- release(int arg):释放同步状态,唤醒等待队列中的线程。
- tryAcquire(int arg):尝试获取同步状态,获取成功返回 true,否则返回 false。
- tryRelease(int arg):尝试释放同步状态,释放成功返回 true,否则返回 false。
AQS 的内部实现主要涉及到以下几个重要的方法:
- acquireQueued(Node node, int arg):将当前线程以节点的形式加入到等待队列中,并自旋等待获取同步状态。
- tryAcquire(int arg):尝试获取同步状态,如果成功返回 true,否则返回 false。
- release(int arg):释放同步状态,唤醒等待队列中的头节点(即等待时间最长的线程)。
- tryRelease(int arg):尝试释放同步状态,如果成功返回 true,否则返回 false。
- addWaiter(Node mode):将当前线程以节点的形式加入到等待队列中。
AQS 的设计使得它可以支持独占锁(Exclusive Lock)和共享锁(Share Lock),并且可以方便地构建更复杂的同步器。通过继承 AQS 并实现 acquire 和 release 方法,开发者可以定制自己的同步器,实现更灵活和高效的并发控制。AQS 的实现为 Java 中锁和同步器的高效、灵活提供了基础。
什么是 AQS定义:AbstarctQueuedSynchronizer 简称 AQS,是一个用于构建锁和同步容器的框架。
事实上 concurrent 包内许多类都是基于 AQS 构建的,例如 ReentrantLock,ReentrantReadWriteLock,FutureTask 等。AQS 解决了在实现同步容器时大量的细节问题。
AQS 使用一个 FIFO 队列表示排队等待锁的线程,队列头结点称作 “哨兵节点” 或者 “哑结点”,它不与任何线程关联。其他的节点与等待线程关联,每个阶段维护一个等待状态 waitStatus。
AQS 提供的两种功能
从使用层面来说,AQS 的锁功能分为两种:独占锁和共享锁。
独占锁:每次只能有一个线程持有锁,比如前面给大家演示的 ReentrantLock 就是以独占方式实现的互斥锁;
共享锁:允许多个线程同时获取锁,并发访问共享资源,比如 ReentrantReadWriteLock。
AQS 的实现依赖内部的同步队列,也就是 FIFO 的双向队列,如果当前线程竞争锁失败,那么 AQS 会把当前线程以及等待状态信息构造成一个 Node 加入到同步队列中,同时再阻塞该线程。当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点 (线程)。
如下图所示,一个节点表示一个线程,它保存着线程的引用(thread)、状态(waitStatus)、前驱节点(prev)、后继节点(next),其实就是个双端双向链表,其数据结构如下:
Tips:AQS 队列内部维护的是一个 FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始,很方便的访问前驱和后继。每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去。
添加线程对于 AQS 队列的变化
当出现锁竞争以及释放锁的时候,AQS 同步队列中的节点会发生变化,首先看一下添加线程的场景。
这里会涉及到两个变化:
- 队列操作的变化:新的线程封装成 Node 节点追加到同步队列中,设置 prev 节点以及修改当前节点的前置节点的 next 节点指向自己;
- tail 指向变化:通过同步器将 tail 重新指向新的尾部节点。
第一个 head 节点表示获取锁成功的节点,当头结点在释放同步状态时,会唤醒后继节点,如果后继节点获得锁成功,会把自己设置为头结点,节点的变化过程如下:
这个过程也是涉及到两个变化:
head 节点指向:修改 head 节点指向下一个获得锁的节点;
新的获得锁的节点:如图所示,第二个节点被 head 指向了,此时将 prev 的指针指向 null,因为它自己本身就是第一个首节点,所以 pre 指向 null。
本文来自博客园,作者:黄橙,转载请注明原文链接:https://www.cnblogs.com/RedOrange/p/18057023
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· 没有Manus邀请码?试试免邀请码的MGX或者开源的OpenManus吧
· 【自荐】一款简洁、开源的在线白板工具 Drawnix
· 园子的第一款AI主题卫衣上架——"HELLO! HOW CAN I ASSIST YOU TODAY
· Docker 太简单,K8s 太复杂?w7panel 让容器管理更轻松!