一:CAS的理解
CAS:比较并替换
基本的实现方式:V-内存地址 A-旧的预期的值,B-要修改的新的值
基本的实现过程:在更新一个变量的时候 只有变量的旧的预期的值A和内存地址的值V相同的时候,才会将V修改成为要修改的新的值B
图示过程
CAS实现的基石unsafe类:
Unsafe :主要是来保持CAS的原子的操作比较和替换动作一致 (官方不推荐和少用该类去操作)
常见问题ABA问题
大致意思就是CAS去操作的内存值V由A改成B又改会成了A 从而导致后续本来不应该成功执行的最后成功执行了
示例演示
解决的方法就是给数据进行加版本号 比如ES就是这样做的
二:AQS的认识
AQS(抽象的队列同步器):
1-是构建锁 或者其他同步组件的基础框架
2-是各种锁或者同步组件实现的 公共基础部分的抽象实现
3-保证同步队列的管理和维护
4-保证同步状态的管理
5-线程的阻塞和唤醒的管理
AQS的基本的设计思路
- 先把来竞争的线程以及等待的状态 封装成为Node对象
- 把这些node对象放到一个同步队列中去 这个同步队列是一个FIFO的一个双向队列 是基于CLH队列来实现的
AQS 使用一个int state来表示同步状态 比如:是否有线程获取锁 锁的重入次数等等 具体的含义有具体的子类自己来进行定义
三:AQS源码分析
AQS里面有一个以node为对象的一个等待队列 采取的是clh队列 大概是以下的结构
Node就是把前来竞争锁的线程 和它们的状态给封装成一个node对象
AQS里面有Head和Tail的节点
Head节点指向第一个node1
Tail节点指向最后一个node4
节点和节点之间是prev指向上一个节点
节点和节点之间是next指向下一个节点
一个node节点里面主要的构成
同步队列的构建过程源码分析
假设当前线程0一直占用到锁
A-起始入口
因为线程0获取到锁 线程1就进入addWaiter方法
该方法 先把当前线程1拿出来生成一个node1
在把AQS的tail值给了pred
tail=null 所以if条件不成立
带着node1进入enq方法
里面一个死循环
因为tail=null
所以进入第一个if语句
创建出一个新的node0 并且把AQS的Head的值 node0 并且tail也指向node0
再次判断 tail!=null
进入esle
当前 t =node0
而node.prev =t
所以node1的prev指向node0
在通过compareAndSetTail 把tail从指向node0 变成指向node1
t.next = node 把node0的next指向node1
在退出循环
放回到上一个方法
addWaiter方法执行完进入 acquireQueued方法
interrupted 是一个判读是否中断的一个状态位
拿出node1的pre值给 p 所以p的值就是node0
node0是head 在去尝试获取锁 当前的条件是线程0占用着锁
假设1:线程0完成任务 释放锁 线程1获取到锁
把head的值从node0指向node1
node0的next断开指向node1
这时候其实就把node0和队列断开连接 接着就等待GC的回收
返回interrupted
假设2:线程0还在使用锁 或者在线程0释放锁之后 锁没有被线程1获取而是被后面的线程获取
则进入第二个if
进入shouldParkAfterFailedAcquire
拿到node0的waitStatus的值为0
所以进去compareAnd...方法 把ws改为SIGNAL
返回上一个方法的死循环 又进入这个方法
再次进入当前方法拿到ws为SIGNAL 所以进入第一个方法
直接return true 在返回上一个方法 进入parkAndCheckInterrupt方法
线程到这里直接进行线程1的park 等待线程的唤醒
同步队列运行期间的维护过程源码分析
当前的条件是线程0获取锁 到线程锁0完成工作要释放锁
走这个方法
条件都生成 不为kull 和node0为SIGNAL 进入unparkSuccessor方法
SIGNAL的值为-1 所以<0 这里讲node0的status改为0
往下 拿到node1给s
判断s 的值不为NUll 进入unpark 此时就把之前的线程1进行唤醒
同步队列运行期间的队列异常源码分析
维护方法在acquireQueued方法里面的finall里面
当线程进行中断了 或者宕机了
前置node中断的处理源码
第一处地方
进入cancelAcquire
private void cancelAcquire(Node node) {
if (node == null)
return;
//这里把当前的线程从这个node里面剔除
node.thread = null;
//取到node之前的线程
Node pred = node.prev;
//这里是如果node的前置节点node1也被取消
while (pred.waitStatus > 0)
//从node节点依次往前去找 直到找到没有被取消的node 把当前的node的pre值指向没有被取消的
node上
node.prev = pred = pred.prev;
Node predNext = pred.next;
//把自己的状态标记为CANCELLED
node.waitStatus = Node.CANCELLED;
//如果node是tail 把AQS的tail设置为node的前一个node1
if (node == tail && compareAndSetTail(node, pred)) {
//node的前置node1的next设置为null 到这里其实就是把这个node和队列断开了联系
compareAndSetNext(pred, predNext, null);
} else {
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//这里就是把node的下一个node2拿到
Node next = node.next;
if (next != null && next.waitStatus <= 0)
//把node的前置node1的next指向node后置node2
compareAndSetNext(pred, predNext, next);
} else {
unparkSuccessor(node);
}
//这里进行断开自己的node 同样就把自己从队列里面进行剔除
node.next = node; // help GC
}
}
第二个处理的地方
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
//这里是如果node的前置node1中断了
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
//进行循环往前推找到一个没有中断的线程 把node的next指向那个正常的node
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
后置node中断的处理源码
第一处理的地方
在release---------->unparkSuccessor里面
Node s = node.next;
//拿到node的后置node2
if (s == null || s.waitStatus > 0) {
s = null;
//把tail的值给了T 从后面逐步往前找 边走 边把t的值一直刷新为t的pre值
//如果刷新的这个t值的状态不为中断状态 则 s的值一直保持和t值一样
//如果刷新的这个t值的状态为中断状态 则 s的值不保持和t值一样
//直到跑到t为第一个node值 终止循环
//此时s为当前node之后第一个没有中断状态的node 把node的next指向这个s
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
欢迎进行学习交流,不足之处请指出,喜欢麻烦点赞+收藏,谢谢各位大佬了