导图社区 AQS
AQS(AbstractQueuedSynchronizer)是Java并发编程中的一个重要类,是用于实现锁和其他同步工具的基础框架。 AQS提供了一种模板方法模式,开发人员可以通过继承AQS并实现其提供的一些方法来构建自己的同步工具。 这份AQS思维导图详细介绍了AQS的实现原理,包括内部类、节点的构成、状态的使用等等。通过这份思维导图,可以更好地理解AQS在Java并发编程中的重要性和使用方法,同时对于学习并发编程有很大的帮助。
编辑于2023-02-16 10:40:17 四川省这份思维导图主要按照《python从入门到实践》的大纲来做出来的,并在相关内容的解释处加入了相关代码,欢迎大家一起学习!
职能地图-Java,干货分享~Java语言技术,java技术扩展,数据结构,维优,个人职能,技术面试知识点总结。
当今大型软件系统的开发多采用企业级的开发模式,而Java语言也是目前较为流行的企业级开发语言之一。针对Java企业级开发,涉及的知识点和技术栈较为丰富,包括但不限于Java EE、Spring框架、Hibernate框架、Maven、Git、Jenkins等等。这份思维导图以Java企业级开发为主题,通过图解的形式将涉及的知识点进行了梳理和整理,从Java EE体系结构、Servlet、JSP、Spring框架、Hibernate框架、Maven等基础知识开始讲解,逐步深入到SpringMVC、
社区模板帮助中心,点此进入>>
这份思维导图主要按照《python从入门到实践》的大纲来做出来的,并在相关内容的解释处加入了相关代码,欢迎大家一起学习!
职能地图-Java,干货分享~Java语言技术,java技术扩展,数据结构,维优,个人职能,技术面试知识点总结。
当今大型软件系统的开发多采用企业级的开发模式,而Java语言也是目前较为流行的企业级开发语言之一。针对Java企业级开发,涉及的知识点和技术栈较为丰富,包括但不限于Java EE、Spring框架、Hibernate框架、Maven、Git、Jenkins等等。这份思维导图以Java企业级开发为主题,通过图解的形式将涉及的知识点进行了梳理和整理,从Java EE体系结构、Servlet、JSP、Spring框架、Hibernate框架、Maven等基础知识开始讲解,逐步深入到SpringMVC、
AQS
简介
AQS是一个用来构建锁和同步器的框架,使用AQS能简单且高效地构造出应用广泛的大量的同步器,比如ReetrantLock,Semaphore等
核心思想
如果被请求的共享资源空闲,则将当前请求的资源的线程设置为有效的工作线程,并且将共享资源设置为锁定状态。
如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制是AQS用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中
CLH
CLH队列是一个虚拟的双向队列。AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配
state
AQS使用int成员变量来表示同步状态,通过内置的FIFO队列来完成获取资源线程的排队工作。AQS使用CAS对该同步状态进行原子操作实现对其值的修改
private volatile int state;//共享变量,使用volatile修饰保证线程可见性
对资源的共享方式
1、独占
概念
只有一个线程能执行,如ReentrantLock。又可以分为公平锁和非公平锁
公平锁
按照线程在队列中的排队顺序,先到者先拿到锁
非公平锁
当线程要获取锁时,无视队列顺序直接去抢锁,谁抢到就是谁的
2、共享
多个线程可以同时执行,如Semaphore/CountDownLatch。Semaphore、CountDownLatCh、 CyclicBarrier、ReadWriteLock
AQS使用的模板方法
概念
同步器的设计是基于模板方法模式的,使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
需要重写的方法
1、isHeldExclusively()
该线程是否正在独占资源。只有用到condition才需要去实现它。
2、tryAcquire(int)
独占方式。尝试获取资源,成功则返回true,失败则返回false。
3、tryRelease(int)
独占方式。尝试释放资源,成功则返回true,失败则返回false。
4、tryAcquireShared(int)
共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
5、tryReleaseShared(int)
共享方式。尝试释放资源,成功则返回true,失败则返回false。
ReentrantLock
state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。 此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。 当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。 但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
AQS的数据结构
图示
数据结构
AbstractQueuedSynchronizer类底层的数据结构是使用CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。
原理
AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配
Sync queue
概念
同步队列,是双向链表,包括head节点和tail节点,head节点主要用作后续的调度
Condition queue
概念
非必须的,是一个单向链表,只有当使用Condition时,才会存在此单项链表。可能出现多个
AQS继承关系
继承
AbstractOwnableSynchronizer
该类中,可以设置独占资源线程和获取独占资源线程。分别为setExclusiveOwnerThread与getExclusiveOwnerThread方法,这两个方法会被子类调用
抽象类源码
AQS的内部类
1、Node类
概念
每个线程被阻塞的线程都会被封装成一个Node节点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态。
节点状态
CANCELLED
值为1,表示当前线程被取消
SIGNAL
值为-1,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作
CONDITION
值为-2,表示当前节点在等待condition,也就是在condition queue中
PROPAGATE
值为-3,表示当前场景下后续的acquireShared能够执行
值为0
表示当前节点在sync queue中,等待着获取锁
2、ConditionObject类
概念
此类实现了Condition接口,Condition接口定义了条件操作规范
Condition接口
属性
long serialVersionUID
版本号
Node firstWaiter
condition队列的头节点
Node lastWaiter
condition队列的尾结点
方法
Node addConditionWaiter()
添加新的waiter到wait队列
void doSignal(Node first)
删除和传输节点,直到命中未取消的节点或 空
void doSignalAll(Node first)
删除和传输所有节点
void unlinkCancelledWaiters()
从condition队列中清除状态为CANCEL的结点
final void signal()
唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁
final void signalAll()
唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
void awaitUninterruptibly()
等待,当前线程在接到信号之前一直处于等待状态,不响应中断
int checkInterruptWhileWaiting(Node node)
检查中断
如果发送信号前中断则返回THROW_IE
如果发送信号后中断返回REINTERRUPT
如果没有中断返回0
void reportInterruptAfterWait(int interruptMode)
取决于入参,执行重新中断当前线程,或不执行任何操作
void await() throws InterruptedException
等待,当前线程在接到信号或被中断之前一直处于等待状态
long awaitNanos(long nanosTimeout)
等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
boolean awaitUntil(Date deadline)
等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
boolean await(long time, TimeUnit unit)
等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于: awaitNanos(unit.toNanos(time)) > 0
boolean isOwnedBy(AbstractQueuedSynchronizer sync)
如果此条件是由给定的同步对象创建的则返回true
boolean hasWaiters()
查询是否有正在等待此条件的任何线程
int getWaitQueueLength()
返回正在等待此条件的线程数估计值
Collection<Thread> getWaitingThreads()
返回包含那些可能正在等待此条件的线程集合
AQS属性
属性
1、final long serialVersionUID
版本号
2、volatile Node head
头节点
3、volatile Node tail
尾结点
4、volatile int state
状态
5、final long spinForTimeoutThreshold
自旋时间
静态属性
1、final Unsafe unsafe = Unsafe.getUnsafe()
Unsafe类实例
2、final long stateOffset
state内存偏移地址
3、final long headOffset
head内存偏移地址
4、final long tailOffset
state内存偏移地址
5、final long waitStatusOffset
tail内存偏移地址
6、final long nextOffset
next内存偏移地址
静态初始化块
AQS构造方法
protected AbstractQueuedSynchronizer() { }
此构造方法为从抽象构造方法,供子类调用
类的核心方法
acquire方法
release方法
AQS使用的模板方法
概念
同步器的设计是基于模板方法模式的,使用者继承AbstractQueuedSynchronizer并重写指定的方法。(这些重写方法很简单,无非是对于共享资源state的获取和释放) 将AQS组合在自定义同步组件的实现中,并调用其模板方法,而这些模板方法会调用使用者重写的方法。
需要重写的方法
1、isHeldExclusively()
该线程是否正在独占资源。只有用到condition才需要去实现它。
2、tryAcquire(int)
独占方式。尝试获取资源,成功则返回true,失败则返回false。
3、tryRelease(int)
独占方式。尝试释放资源,成功则返回true,失败则返回false。
4、tryAcquireShared(int)
共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
5、tryReleaseShared(int)
共享方式。尝试释放资源,成功则返回true,失败则返回false。
ReentrantLock
state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。 此后,其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。 当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。 但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。
AQS的数据结构
图示
数据结构
AbstractQueuedSynchronizer类底层的数据结构是使用CLH(Craig,Landin,and Hagersten)队列是一个虚拟的双向队列(虚拟的双向队列即不存在队列实例,仅存在结点之间的关联关系)。
原理
AQS是将每条请求共享资源的线程封装成一个CLH锁队列的一个结点(Node)来实现锁的分配
Sync queue
概念
同步队列,是双向链表,包括head节点和tail节点,head节点主要用作后续的调度
Condition queue
概念
非必须的,是一个单向链表,只有当使用Condition时,才会存在此单项链表。可能出现多个
AQS继承关系
继承
AbstractOwnableSynchronizer
该类中,可以设置独占资源线程和获取独占资源线程。分别为setExclusiveOwnerThread与getExclusiveOwnerThread方法,这两个方法会被子类调用
抽象类源码
类的核心方法
acquire方法
概念
该方法以独占模式获取(资源),忽略中断,即线程在aquire过程中,中断此线程是无效的
源码
调用方法流程图
流程详解
1、调用tryAcquire方法
调用此方法的线程会试图在独占模式下获取对象状态。此方法应该查询是否允许它在独占模式下获取对象状态,如果允许,则获取它
在AQS源码中默认会抛出一个异常,即需要子类重写此方法完成自己的逻辑
2、tryAcquire失败,调用addWaiter方法
此方法完成的功能是将调用此方法的线程封装成为一个节点并放入Sync queue
3、调用accquireQueued方法
此方法实现Sync queue中的节点不断尝试获取资源,若成功,则返回true,否则,返回false
addWaiter方法详解
源码
addWaiter方法
enq方法
说明
使用快速添加的方式往sync queue尾部添加节点。如果sync queue队列还没有初始化,则会使用enq插入队列中
enq会使用无限循环来确保节点的成功插入
acquireQueued方法详解
源码
acquireQueued方法
shouldParkAfterFailedAcquire方法
parkAndCheckInterrupt方法
cancelAcquire
unparkSuccessor
说明
1、首先获取当前节点的前驱节点
2、如果前驱节点是头节点并且能够获取(资源),代表该节点能够占有锁,设置头节点为当前节点
3、如果前驱节点不是头节点,调用shouldParkAfterFailedAcquire和parkAndCheckInterrupt
4、只有当前节点的前驱节点状态为SIGNAL时,才可以对该节点封装的线程进行park操作。否则,将不能进行park操作
5、cancelAcquire作用是取消当前线程对资源的获取,即设置该节点的状态为CACELLED
6、unparkSuccessor作用是释放node节点的后继节点
node为参数,在执行完cancelAcquire方法后的效果就是unpark了s节点所包含的t4线程
整体流程
1、判断节点的前驱节点是否为head并且是否成功获取资源
2、若步骤1均满足,则设置节点为head,之后会判断是否finally模块,然后返回
3、若步骤2不满足,则判断是否需要park当前线程,是否需要park当前线程的逻辑是判断节点的前驱节点的状态是否为SIGNAL
是
则park当前节点
否
不进行park操作
4、如果park了当前线程,之后某个线程对本线程unpark后,并且本线程也获得机会运行。那么,将会继续进行步骤1的判断
release方法
概念
以独占模式释放对象
源码
说明
1、tryRelease的默认实现是抛出异常,需要具体的子类实现
2、tryRelease成功,那么如果头节点不为空并且头节点的状态不为0,则释放头节点的后继节点
AQS属性
属性
1、final long serialVersionUID
版本号
2、volatile Node head
头节点
3、volatile Node tail
尾结点
4、volatile int state
状态
5、final long spinForTimeoutThreshold
自旋时间
静态属性
1、final Unsafe unsafe = Unsafe.getUnsafe()
Unsafe类实例
2、final long stateOffset
state内存偏移地址
3、final long headOffset
head内存偏移地址
4、final long tailOffset
state内存偏移地址
5、final long waitStatusOffset
tail内存偏移地址
6、final long nextOffset
next内存偏移地址
静态初始化块
AQS的内部类
1、Node类
概念
每个线程被阻塞的线程都会被封装成一个Node节点,放入队列。每个节点包含了一个Thread类型的引用,并且每个节点都存在一个状态。
节点状态
CANCELLED
值为1,表示当前线程被取消
SIGNAL
值为-1,表示当前节点的后继节点包含的线程需要运行,需要进行unpark操作
CONDITION
值为-2,表示当前节点在等待condition,也就是在condition queue中
PROPAGATE
值为-3,表示当前场景下后续的acquireShared能够执行
值为0
表示当前节点在sync queue中,等待着获取锁
2、ConditionObject类
概念
此类实现了Condition接口,Condition接口定义了条件操作规范
Condition接口
属性
long serialVersionUID
版本号
Node firstWaiter
condition队列的头节点
Node lastWaiter
condition队列的尾结点
方法
Node addConditionWaiter()
添加新的waiter到wait队列
void doSignal(Node first)
删除和传输节点,直到命中未取消的节点或 空
void doSignalAll(Node first)
删除和传输所有节点
void unlinkCancelledWaiters()
从condition队列中清除状态为CANCEL的结点
final void signal()
唤醒一个等待线程。如果所有的线程都在等待此条件,则选择其中的一个唤醒。在从 await 返回之前,该线程必须重新获取锁
final void signalAll()
唤醒所有等待线程。如果所有的线程都在等待此条件,则唤醒所有线程。在从 await 返回之前,每个线程都必须重新获取锁。
void awaitUninterruptibly()
等待,当前线程在接到信号之前一直处于等待状态,不响应中断
int checkInterruptWhileWaiting(Node node)
检查中断
如果发送信号前中断则返回THROW_IE
如果发送信号后中断返回REINTERRUPT
如果没有中断返回0
void reportInterruptAfterWait(int interruptMode)
取决于入参,执行重新中断当前线程,或不执行任何操作
void await() throws InterruptedException
等待,当前线程在接到信号或被中断之前一直处于等待状态
long awaitNanos(long nanosTimeout)
等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态
boolean awaitUntil(Date deadline)
等待,当前线程在接到信号、被中断或到达指定最后期限之前一直处于等待状态
boolean await(long time, TimeUnit unit)
等待,当前线程在接到信号、被中断或到达指定等待时间之前一直处于等待状态。此方法在行为上等效于: awaitNanos(unit.toNanos(time)) > 0
boolean isOwnedBy(AbstractQueuedSynchronizer sync)
如果此条件是由给定的同步对象创建的则返回true
boolean hasWaiters()
查询是否有正在等待此条件的任何线程
int getWaitQueueLength()
返回正在等待此条件的线程数估计值
Collection<Thread> getWaitingThreads()
返回包含那些可能正在等待此条件的线程集合