AQS相关笔记
电脑修了快20天了,还没修好,我服了。。。
也没有好记笔记和学习的地方,所以干脆在这里记笔记好了。
AQS
AQS具备特性:
1. 阻塞等待队列
2. 共享/独占
3. 公平/非公平
4. 可重入
5. 允许中断
ReetrantLock
阻塞:
LockSupport.park();
唤醒:
LockSupport.unpark(Thread t);
Lock锁伪代码:
Lock
三大核心原理:
自旋、LockSupport、CAS、队列;
怎么获取锁?获取锁的是哪个线程?依赖的什么东西构建的队列?
公平
exclusiveOwnerThread 当前获取锁的线程是谁?
state 状态器 - 用于记录当前锁的状态 int类型,默认值为0
基于Node内部类构建队列,里面主要有 head、tail、thread属性。它是一个双向链表。
ReentrantLock
lock方法内部是调用的aquire(1):
aquire方法在父类:AbstractQueuedSynchronizer内部,aquire方法源码如下:
public final void acquire(int arg) { if(!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXECUTIVE), arg)){ selfInterrupt(); // 外围程序员自己定义的代码也要能够识别到中断信号,把中断信号往外传 } }
这里有一个tryAcquire(arg)的方法,在其父类:AbstractQueuedSynchronizer 是一个空的实现,需要子类去实现它。
其中ReentrantLock源码该方法如下:
1. 如果当前state为0,并且当前队列里面没有人在等,并且cas更改数据成功,那么就拿到锁了,并且更改 exclusiveOwnerThread 为当前获取锁的当前线程。
2. 如果当前state不为0,但是如果当前持有锁的线程是当前线程自己,那么state + 1;如果持有锁的当前线程不是自己,那么就return false,即拿不到锁。
图解结构表示为:
如果像t1,t2,t3这些线程没有加锁成功,就会进入 acquireQueued(addWaiter(Node.EXECUTIVE), arg) 方法的逻辑:
addWaiter方法源码如下:
addWaiter(Node.EXECUTIVE):线程入队,Node节点,Node对Thread引用
Node:共享属性,独占属性
创建节点Node = pre,next,waitstate,thread 重要属性
waitState 节点的生命状态:信号量
- SINGAL = -1 // 可被唤醒
- CANCELLED = 1 // 代表出现异常,中断引起的,需要废弃结束
- CONDITION = -2 // 条件等待
- PROGATATE -3 // 传播
- 0 - 初始状态Init状态
为了保证所有的阻塞对象能被唤醒,入队时也需要保证安全
compareAndSetTail(t, node) 入队也存在竞争
// 当前节点,线程要开始阻塞
acquireQueued(Node(currentThread), arg)
节点在阻塞之前还得再尝试一次获取锁
1. 能够获取到,节点出队,并且把head往后面挪一个节点,新的头结点就是当前节点。
2. 不能获取到,阻塞等待被唤醒。
1. 首先第一轮循环修改head的状态,修改成SINGAL 标记出可以被唤醒。
2. 第二轮循环循环,阻塞线程。parkAndCheckInterrupt(),并且判断是否是有中断信号唤醒的!
acquireQueued(Node(currentThread), arg) 方法内部会有一个自旋的过程(for(;;)),在两次尝试都获取不到锁之后,会进入下面的方法判断,是否可以进入可被唤醒状态。
boolean shouldParkAfterFailedAcquire(Node pred, Node node) 分别代表前驱结点和当前节点
如果前驱节点对应状态为SINGAL (可被唤醒),则返回true. -- 之后就会进入阻塞状态。
如果前驱节点对应状态值 > 0 ,则代表出现了异常,
如果前驱节点对应状态值 < 0,则把前驱节点状态 改为 SINGAL(通过cas操作改变)
waitState = 0 -> -1 head节点为什么改到 -1,因为持有锁的线程 T0 在释放锁的时候,得判断head节点的waitstate是否 != 0,如果!=0成立,会再把waitState = -1 -> 0,要想唤醒排队的第一个线程T1,T1唤醒再接着走循环,去抢锁,可能会再失败(在非公平锁场景下),此时可能有线程T3持有了锁!T1可能再次被阻塞,head节点状态需要再一次经历两轮循环:waitstate =0 -> -1
Park阻塞线程唤醒有两种方式:
1. 中断
2. relase
除了lock方法以外,还有一个lockInterruptibly()的方法,在其内部,可以发现,如果发现中断,是直接抛出中断异常的,最终调用finally代码块中的cancelAcquire(node)方法。
线程中断被唤醒,抛异常。
cancelAcquire(node): // 标注当前节点的生命状态为cancelled.
参考:https://blog.csdn.net/qq_40239169/article/details/125534173
end!
如果您觉得阅读本文对您有帮助,请点一下“推荐”按钮,您的“推荐”将是我最大的写作动力!欢迎各位转载,但是未经作者本人同意,转载文章之后必须在文章页面明显位置给出作者和原文连接,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
2020-10-28 工资计算方式
2020-10-28 什么样的辞职理由能让面试官满意
2019-10-28 mysql—查询数据库表的数量
2019-10-28 mysql赋权收回权限操作