type
status
date
slug
summary
tags
category
icon
password
这里写文章的前言:
一个简单的开头,简述这篇文章讨论的问题、目标、人物、背景是什么?并简述你给出的答案。
可以说说你的故事:阻碍、努力、结果成果,意外与转折。
📝 基本介绍
AQS(队列同步器),简称同步锁框架。
同步器是实现锁的关键,利用同步器将锁的语义实现,然后在锁的实现中聚合同步器。
AQS会将请求获取锁失败的线程放入一个队列的尾部:等待获取锁的线程全部处理阻塞状态。当前线程执行完毕(释放锁)后,会激活当前线程的后继节点。
- 采用模板模式,tryacquire(), release()等方法需要子类覆写。实现了算法主体框架,供外部调用,里面会调用原语操作和钩子操作
- 原语操作:即定义的抽象方法,子类必须重写
- 钩子操作:与原语操作类似,也是供子类重写的。区别是钩子可以重写也可以不重写,如果不重写默认使用父类的
- 一般的lock类都实现Lock接口的,而内部会继承AQS,因为Lock是面向使用者,而AQS这个框架是锁的实现框架,使用者一般不会关注AQS
CLH队列
介绍
CLH锁其实就是一种基于队列(具体为单向链表)排队的自旋锁,由于是Craig、Landin和Hagersten三人一起发明的,因此被命名为CLH 锁,也叫CLH队列锁
简单的CLH锁可以基于单向链表实现,申请加锁的线程首先会通过 CAS操作在单向链表的尾部增加一个节点,之后该线程只需要在其前驱节点上进行普通自旋,等待前驱节点释放锁即可
由于CLH锁只有在节点入队时进行一下CAS的操作,在节点加入队列之后,抢锁线程不需要 进行CAS自旋,只需普通自旋即可。因此,在争用激烈的场景下,CLH 锁能大大减少CAS操作的数量,以避免CPU的总线风暴
声明一个node节点,locked是自身,myPred是前一个node节点的引用(组成一个单向链表),因为获取锁的线程,可能是多个,后节点会不断的自旋(普通自旋)自己的myPred指向的前一个node的Locked属性,当这个属性探测得到是false的时候,说明前驱节点已经释放了锁,自己应该去抢锁了

方法
重写方法
方法名称 | 描述 |
boolean tryAcquire(int arg) | 独占式获取同步状态,成功返回true,失败返回false |
boolean tryRelease(int arg) | 独占式释放同步状态,成功返回true,失败返回false |
int tryAcquireShared(int arg) | 共享式获取同步状态,获取成功则返回值>=0 |
boolean tryReleaseShared(int arg) | 共享式释放同步状态,成功返回true,失败返回false |
boolean isHeldExclusively() | 判断同步器是否在独占模式下被占用,一般用来表示同步器是否被当前线程占用 |
重写模板方法
子类需要重写该方法,AQS中提供的模板方法才可以正常调用
方法名称 | 描述 |
void acquire(int arg) | 独占式获取同步状态,该方法会调用子类重写的tryAcquire(int arg),如果tryAcquire返回true则该方法直接返回,否则先将当前线程加入同步队列的尾部,然后阻塞当前线程 |
void acquireInterruptibly(int arg) | 当线程获取同步状态失败被阻塞后,可以响应中断,收到中断后将会取消获取同步状态 |
boolean tryAcquireNanos(int arg, long nanosTimeout) | 在acquireInterruptibly的基础上加了超时限制,如果在超时时间内获取到同步状态返回true,否则返回false |
boolean release(int arg) | 独占式释放同步状态,该方法会在释放同步状态后将第一个节点(对应刚刚释放同步状态的线程)的后继节点对应的线程唤醒 |
void acquireShared(int arg) | 共享式获取同步状态,该方法会调用子类重写的tryAcquireShared(int arg),如果tryAcquireShared返回true则该方法直接返回,否则先将当前线程加入同步队列的尾部,然后阻塞当前线程 |
void acquireSharedInterruptibly(int arg) | 当线程获取同步状态失败被阻塞后,可以响应中断,收到中断后将会取消获取同步状态 |
boolean tryAcquireSharedNanos(int arg, long nanosTimeout) | 在acquireSharedInterruptibly的基础上加了超时限制,如果在超时时间内获取到同步状态返回true,否则返回false |
boolean releaseShared(int arg) | 共享式的释放同步状态 |
State
volatile关键字进行修饰
0=初始值,没有被线程占用
1=锁已经被抢占(抢占之后调用unlock方法会释放锁,将state=1重置为0
1的情况说,说明锁被重入了。举例子:该值等于3,说明已经被重入了3次,最后递减state的时候也应该递减3次,重置为0的时候表示已经释放了锁
Node
AQS的Node
FIFO队列

AQS结构

AQS执行流程
node节点会通过CAS算法把自己变成读取到尾部node节点的下一个节点,不成功的继续自旋读取最新的尾节点再CAS


acquire 方法

acquireQueued 方法
1. node.predecessor 找到前一个节点,如果是head节点的话,就CAS获取锁(业务执行块的话,前面锁释放了,这时候尝试获取一些)
2. 如果获取成功的话,当前节点设置头节点,将原来的head节点设置为null
3. 如果1 和 2 不成功,走 shouldParkAfterFailedAcquire,检查自己是不是应该park,如果park之后,交给上一个线程去唤醒
release 方法
释放锁的时候,会唤起head节点的下一个节点,当后置节点拿到锁之后,会把head指向自己
🤗 总结归纳
📎 参考文章
有关文章的问题,欢迎您在底部评论区留言,一起交流~