CAS和AQS的(队列创建和维护和队列异常处理)源码分析和了解

一: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的基本的设计思路

  1. 先把来竞争的线程以及等待的状态 封装成为Node对象
  2. 把这些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;
        }

欢迎进行学习交流,不足之处请指出,喜欢麻烦点赞+收藏,谢谢各位大佬了

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值