前言
Java同步器AbstractQueuedSynchronizer简称AQS(文中全称和简写混用),在java.util.concurrent包中很多依赖状态的API都是基于AQS实现的,比如常用的:ReentrantLock、Semaphore、CountDownLatch、ThreadPoolExecutor等等。
可以说AQS是java并发包实现的基石,深入理解AQS可以帮助我们更好的是理解java并发api,而不仅仅停留在使用上。同时我们也可以基于AQS实现一些自定义的可阻塞类,虽然大部分时候不需要我们这样做,因为使用现有的api基本已经足够了。
AbstractQueuedSynchronizer直译过来是“抽象的同步队列”,也就是说AQS本质上是维护一个队列,至于你想用这个队列来做什么AQS不管,具体由子类来定。既然AQS是抽象类,就必须要有子类去实现,但在java.util.concurrent包中没有直接直接实现AQS的子类api,其子类都是作为私有的内部类,在各个API使用。下图为jdk1.8中对AQS实现的子类(在jdk1.6中更多):
可以看到一共有11个子类,并且都内部类。AQS在java.util.concurrent并发包中,主要有以下4个功能:
1、实现锁:ReentrantLock(重入锁)、ReentrantReadWriteLock(重入读写锁),并同时提供公平锁和非公平锁实现。
2、实现信号量:Semaphore(主要用于控制某个资源,可以被同时访问的线程个数),并同时支持公平和非公平实现。
3、实现闭锁:CountDownLatch,一种同步辅助工具,可以实现:在一组其他线程中执行的操作完成之前,阻塞一个或多个线程;并在完成之后,同时唤起被阻塞的一个或者多个线程。
4、实现线程池执行器:ThreadPoolExecutor,这时java线程池框架的基石。
AQS的基本构成
节点定义类Node
前面提到AQS本质上是维护一个队列,首先来看下这个队列中的成员:Node,它是AQS定义的内部类,主要成员变量信息如下:
static final class Node { volatile int waitStatus;//节点状态 volatile Node prev;//前指针 指向前一个节点 volatile Node next;//后指针指向后一个节点 volatile Thread thread;//线程实例 Node nextWaiter;//下一个等待节点 /**状态列表,对应waitStatus字段值*/ //共享类型 static final Node SHARED = new Node(); //独占类型 static final Node EXCLUSIVE = null; //线程取消类型 static final int CANCELLED = 1; //线程唤醒状态类型 对应condition的 signal、signalAll方法 static final int SIGNAL = -1; //线程阻塞状态类型,对应condition的await方法 static final int CONDITION = -2; //对应共享类型释放资源时,传播唤醒线程状态 static final int PROPAGATE = -3; //省略其他 }
可以看到每个节点中都有一个“线程实例”,以及该线程所处的状态waitStatus,以及用于表示前后指针的成员prev、nex,即AQS是双向链表,节点里的“线程实例”其实就是阻塞的线程列表。AQS的主要方法其实就是操作和维护这个双向链表。
成员变量
在来看下AQS的主要成员变量:
//队列的头节点 private transient volatile Node head; //队列的尾节点 private transient volatile Node tail; //资源 状态 private volatile int state;
其中最重要的就是队列状态state(注意跟Node中的节点状态区分开),这个字段一般由于表示一种共享的“资源”的状态,比如:在ReentrantLock锁的实现中,它表示锁是否被占用(为0是表示可用);在信号量Semaphore的实现中,表示剩余的“许可数量”,大于0表示可用;CountDownLatch中表示需全部完成的工作数量。
AQS的核心功能就是:在多线程竞争有限的“资源”的情况下,只允许部分线程(或单个线程)访问这种“资源”,并阻塞其他“线程”。AQS的所有方法几乎都是在根据“资源”的状态,操作和维护一个“双向链表”。
主要方法
前面提到过,由于资源是有限的,所以AQS的核心方法分为两大类:获取“资源”方法和释放“资源”方法,同时这两类方法又都有公平和非公平实现。另外对应 获取“资源”方法,还有延时获取和可中断获取。由于篇幅有限这里不贴出所有的方法,只分类讲解入口方法的主要流程,如果要深入每个方法,可以通过这些入口方法断点跟下去即可。下面分别来看:
独占获取和释放方法
独占获取方法acquire,对应的释放方法release。独占的方式获取“资源”,那这个“资源”状态state其实就只有两种:可以、非可用,一般用于实现独占锁。首先来看acquire方法:
public final void acquire(int arg) { if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) selfInterrupt(); }
简易流程如下:
通过调用tryAcquire(arg)方法尝试获取“资源”,如果获取到 该线程继续执行;否则调用acquireQueued方法加入队列,并阻塞该线程,注意节点是Node.EXCLUSIVE独占方式。其中tryAcquire(arg)方法是交给子类去实现的,在jdk的api中独占方式一般都是用于获取“锁”,具体的实现可以看下ReentrantLock、ReentrantReadWriteLock中的写锁、ThreadPoolExecutor。
再来看下释放资源方法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; }
简易流程如下:
通过调用tryRelease(arg)方法尝试释放资源,其实就是修改“资源”状态state;如果资源释放成功就调用unparkSuccessor方法,唤醒队列头结点的线程(具体是在上述acquireQueued方法中进行阻塞的),继续执行。同样 tryRelease(arg)方法是在子类中实现。
共享获取和释放方法
共享获取方法acquireShared,对应的释放方法为releaseShared。在java的api中主要用于实现:ReentrantReadWriteLock中的读锁(共享锁)、Semaphore。首先看下共享获取方法acquireShared:
public final void acquireShared(int arg) { if (tryAcquireShared(arg) < 0) doAcquireShared(arg); }
通过tryAcquireShared(arg)方法获取“资源”,如果获取到“资源”直接返回,该线程继续执行。如果没有获取到资源,就通过调用doAcquireShared加入队列,并阻塞该线程。这里tryAcquireShared(arg)方法是有子类实现的,具体实现可以参考ReentrantReadWriteLock中的读锁、Semaphore。ReentrantReadWriteLock的获取读锁实现要稍微复杂些,需要判断是否被读锁占有;Semaphore的实现比较简单,就是判断剩余“许可数量”是否大于0。再来看下释放方法releaseShared:
public final boolean releaseShared(int arg) { if (tryReleaseShared(arg)) { doReleaseShared(); return true; } return false; }
通过tryReleaseShared(arg)方法尝试释放资源,如果释放成功,调用doReleaseShared方法唤醒在上述acquireShared 方法中阻塞的线程。同样的tryReleaseShared(arg)方法是在子类中实现的。
共享获取、排它独占获取,二者几乎没有区别,只是在释放方法中有区别,独占方式释放资源后,只会唤醒“头结点”的线程;而共享方式,会遍历队列,把满足条件的线程全部唤醒,具体可以参考doReleaseShared和acquireQueued方法。
可中断获取方法
可中断获取方法acquireInterruptibly(独占可中断获取)、acquireSharedInterruptibly(共享可中断获取),这两个方法中的独占和共享的实现与上述讲述过程相同,不再累述。如果获取不成功就进入排队并阻塞当前线程,唯一的区别就是会抛出InterruptedException异常,也就是说可以在其他线程调用该线程的interrupt方法,中断该线程放弃任务执行,从而放弃排队。以acquireInterruptibly方法实现为例,如下:
public final void acquireInterruptibly(int arg) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); if (!tryAcquire(arg)) doAcquireInterruptibly(arg); }
与独占获取方法一样 通过tryAcquire(arg)方法获取资源,如果没有获取到就调用doAcquireInterruptibly(arg)方法把当前线程加入队列,并阻塞当前线程。
延迟获取方法
延迟获取方法tryAcquireNanos(独占延迟获取)、tryAcquireSharedNanos(共享延迟获取),这两个方法中的独占和共享的实现与上述讲述过程相同,不再累述。如果获取不成功就进入排队并阻塞当前线程,唯一的区别就是这里阻塞会有时间限制,如果超时就抛出InterruptedException异常,中断该线程放弃任务执行,从而放弃排队。
注意与“可中断获取方法”的区别:前者是时间到了,自动中断线程放弃排队;后者是需要外部手动触发。这里以tryAcquireNanos(独占延迟获取)为例,代码实现如下:
public final boolean tryAcquireNanos(int arg, long nanosTimeout) throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); return tryAcquire(arg) || doAcquireNanos(arg, nanosTimeout); }
与独占获取方法一样 通过tryAcquire(arg)方法获取资源,如果没有获取到就调用doAcquireNanos方法把该线程加入队列,并阻塞该线程,与acquireQueued方法不同地方就是阻塞会有一个时间限制,当阻塞时间到达时抛出InterruptedException异常。
可以看到AQS的核心方法分为三类:入口方法,tryXXX方法,doXXX方法。
入口方法:前面已经列出;
tryXXX方法种:有4个方法是交给子类实现的:tryAcquire(尝试独占获取资源)、tryRelease(尝试独占释放资源)、tryAcquireShared(尝试共享获取资源)、tryReleaseShared(尝试释放共享资源)。
doXXX方法:是具体的实现加入队列,以及阻塞线程的核心实现,由于这些方法的代码比较长,就不贴出来了,可以根据上述思路自行查阅jdk API源码。
公平和非公平
所谓公平,就是所有的线程来获取“资源”时,都得按照FIFO的原则排队;所谓非公平,也不是说不排队,而是先检查“资源”是否可用,如果可用就插队立即使用,否则再进行排队。另外公平和非公平实现,都是在子类中实现的,在AQS中没有实现。比如ReentrantLock公平锁和非公平锁实现。
内部类 ConditionObject
最后提下AQS中的内部类:“条件队列”实现类ConditionObject,这个类是Condition接口的实现类(主要方法:await系列方法、signal、signalAll,对应Object类的wait、notify、notifyAll方法),主要用于结合Lock锁结合使用(通过Lock的newCondition()方法得到)。每一个Lock对应一个Condition条件队列,这个条件队列使用的是AQS中的同一个队列,只是队列Node节点中的waitStatus类型不同。比如ReentrantLock锁,通过lock方法可以向AQS中加入节点,也可以通过Condition的await方法向AQS加入节点,只是节点的类型不同而已。
由于条件队列Condition和Lock锁密切相关,关于这部分内容后面有时间再单独总结,这里不再继续展开。
总结
简单的总结AQS,本质上就是维护了一个“双向链表”结构的队列,其中每个节点保存了一个阻塞的线程;其主要作用就是用于多线程竞争有限的资源时,对线程阻塞、排队。另外我们也可以根据AQS实现自己的同步器,当然大部分时候使用现有的API实现类已经足够了。
相关推荐
Java同步框架AbstractQueuedSynchronizer详解 AbstractQueuedSynchronizer(AQS)是Java中的一个同步框架类,它实现了最核心的多线程同步的语义。AQS提供了一个基础的同步器实现,开发者可以通过继承AQS来实现自己...
基于JDK层面的锁主要可以分为四种方式,其中AbstractQueuedSynchronizer(AQS)是Java SDK并发包中主要的同步器实现。 AQS是Java中解决同步和互斥问题的基础同步器,通过Lock和Condition两个接口来实现管程。Lock...
Java AQS(AbstractQueuedSynchronizer)是一个强大的同步组件,它被广泛应用于并发编程中,如Java的Lock接口的实现。AQS的核心是通过一个FIFO(先进先出)的双向队列来管理线程的等待状态。这个队列分为两种类型:...
AbstractQueuedSynchronizer(AQS)是 Java 并发编程中的一个核心组件,提供了一套多线程访问共享资源的同步器框架。AQS 定义了两种资源共享方式:Exclusive(独占)和 Share(共享)。在 AQS 中,维护了一个 ...
总的来说,AQS 是Java并发编程中不可或缺的一部分,它提供了一种通用的框架,使得开发者能够方便地创建高效、灵活的同步器,实现复杂的并发控制逻辑。理解AQS的工作原理对于优化并发程序和设计自己的同步组件至关...
其中,AbstractQueuedSynchronizer(简称AQS)是构建各种同步器的核心组件。 AQS是一个抽象的队列同步器,它基于FIFO(先进先出)队列来管理线程的排队和获取共享资源。它是实现Java并发包中锁和其他同步器的基础...
AQS是Java并发库的核心,它提供了一种抽象的同步器框架。作为一个抽象类,AQS维护了一个双端FIFO等待队列,用于存储等待获取同步状态的线程。AQS提供了基础的原子操作,如`compareAndSet`,用于线程之间的状态更新和...
在Java并发编程领域,AbstractQueuedSynchronizer(AQS)是一个非常重要的基础组件,它是Java并发包java.util.concurrent中实现锁和同步器的核心工具类。AQS通过维护一个FIFO的等待队列来管理线程的同步状态,它提供...
AQS,全称为AbstractQueuedSynchronizer,是一个抽象类,为构建实现阻塞锁和相关同步器(如信号量、事件等)提供了一种基础框架。它内部基于一个FIFO(先进先出)的等待队列来管理线程的同步状态。AQS的设计理念是将...
它提供了一个同步器的框架,其中包含了共享资源的状态管理、线程排队机制等核心功能。开发者可以通过继承AQS并实现其模板方法来构建具体的同步组件,例如`ReentrantLock`、`Semaphore`、`CountDownLatch`等。 #### ...
AQS全称为AbstractQueuedSynchronizer,是java中用于构建锁以及其他同步器的一个框架。在多线程的编程中,同步问题是一个非常重要的问题,而AQS正是为了解决这个问题而生的。 首先,我们需要了解的是AQS的核心思想...
`AQS`(AbstractQueuedSynchronizer)是JUC库中的一个关键组件,它是一个抽象基类,为构建自定义的同步器提供了基础框架。AQS通过内部维护一个基于链表的等待队列,有效地管理线程的同步和唤醒,从而实现锁和其他同步...
AQS(AbstractQueuedSynchronizer)是Java.util.concurrent包中同步器的基础框架,它的核心设计思想与实现方法在Doug Lea先生的这篇论文中有详细的介绍。论文详细阐述了AQS框架的原理、设计、实现、应用以及性能等...
AbstractQueuedSynchronizer(AQS)是Java并发编程库(java.util.concurrent)中的核心组件,它为实现锁和同步器提供了基础框架。AQS利用了一个内置的FIFO(先进先出)双端队列来管理线程的等待和唤醒。在这个队列中...
- **AQS同步队列**:AbstractQueuedSynchronizer,提供了一个框架来实现锁和同步器。 - **CAS无锁的概念**:Compare and Swap,乐观锁的一种实现。 - **常见的原子操作类**:AtomicInteger、AtomicLong等。 - **...
`java.util.concurrent`包中的`AbstractQueuedSynchronizer`框架为Java开发者提供了一个强大的工具箱,使得他们能够高效地实现各种同步器。通过对同步状态的有效管理、阻塞和唤醒机制的优化以及灵活的扩展性设计,...
AQS 是队列同步器 AbstractQueuedSynchronizer 的简写,用来构建锁或者其他同步的基础。AQS 使用一个 int 成员变量来表示同步状态,通过内置 FIFO 队列完成竞争资源的线程排队工作。AQS 提供方法主要有三类:独占式...
4. **同步器(Synchronizers)**:JUC库中的同步器主要是通过AQS(AbstractQueuedSynchronizer)来实现的。AQS是一个抽象类,为锁和同步器提供了一种通用的底层实现。它维护了一个等待队列,提供了基于FIFO的等待...
AbstractQueuedSynchronizer(AQS)是Java并发编程中的核心组件,它是一个抽象的、基于FIFO(先进先出)等待队列的同步器。许多并发工具类,如ReentrantLock、CountDownLatch、CyclicBarrier和Semaphore等,都基于...