面试被问到CAS原理,触及知识盲区,脸都绿了!

面试被问到CAS原理,触及知识盲区,脸都绿了!CAS底层原理想要了解CAS底层原理,那么我们先来了解一下JUC包的一个类即:AtomicInteger。那么这个AtomicInteger有什么用呢?我们平时开发或多或少都会使用到i++这个操作,那么稍微了解多线程的同学都会知道,在多线程环境下,i++操作是线程不安全的操作,譬如下面这段代码:publicclassMain{privateinti=0;publicvoidaddI(){i++;}..

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用


前提知识掌握

想要了解 CAS 底层原理,那么我们先来了解一下 java.uit.concurrent即JUC包的一个类即:AtomicInteger

那么 这个 AtomicInteger 有什么用呢 ?

我们平时开发或多或少都会使用到 i++ 这个操作,那么稍微了解多线程的同学都会知道,在多线程环境下,i++ 操作是线程不安全的操作,譬如下面这段代码:

public class Main { 
   

    private int i = 0;

    public void addI() { 
   
        i++;
    }

    public static void main(String[] args) throws InterruptedException { 
   
        Main main = new Main();
        for (int i = 1; i <= 100; i++) { 
   
            new Thread(() -> { 
   
                for (int j = 1; j <= 200; j++) { 
   
                    main.addI();
                }
            },String.valueOf(i)).start();
        }
        //两个线程 主线程 和 垃圾回收线程
        while (Thread.activeCount() > 2) { 
   
            Thread.yield();
        }
        System.out.println(Thread.currentThread().getName() + "\t" + main.i);
    }

}

这段代码很简单,就是开启100个线程,每个线程进行200次 i++操作,那么我们来看看最后的答案:

在这里插入图片描述

正常来说这个 i 最后的值应该是 20000,但是这里却是19884,当然来,并不是这个值每次都是19884,小伙伴们可以多试几次,但这些值都有一个共同的特性,就是基本上都是小于20000的(当然可能有时候就是20000,可以把线程数开多点,或者每个线程进行 i++次数弄多点)。

这是为什么呢?

原因就是:i++其实不是一个原子性操作,什么叫原子性操作?简单来说就是不可再拆分的操作,而 i++是可以继续拆分的操作,即非原子性操作,它的操作可以分为以下三步:

  1. 取出 i 的值
  2. 将 i 的值 进行 +1
  3. 将 i+1 后的值写回给 i(也就是写回给主内存)

这里涉及到 Java内存模型,不清楚的小伙伴可以参考参考这篇博文哦 ——> Java 内存模型(JMM)、happens-before规则(先行发生规则) 你能知多少?

假设: 现在 i = 1,在多线程环境中,可能线程A正在进行第二步操作,但此时线程B飞快地完成这三部操作,那么此时 i 的值为2,但是线程A并不知道 i 已经为2了,它还会继续往下执行,那么对于线程A来说 会将 i+1的值写回给 i 也就是将 2写回给i,那么最后 i 的值为2不是3就错误了。

那么就会有小伙伴说了,照你这么说直接用 volatile 修饰 i 不就行了。
在这里插入图片描述

话不多说,先上代码

在这里插入图片描述

在这里插入图片描述

这是为什么呢?

我们都知道 volatile进行写操作时会把最新的变量值强制刷新到主内存,还会导致其他线程里的缓存无效。也就是说,当线程B完成i++操作之后,线程A缓存的 i 的值会无效。

在这里插入图片描述

但是
我们设想一下这样的场景, i = 10,如果线程A完成i+1操作后准备写回到主内存时阻塞了,此时B线程来了,上来就是一套 接——>化——>发,哦不不不,是一套拷贝、赋值、写入,啪一下,很快啊,B就搞完了,此时 i 为11,那么虽说由volatile保证可见性,但是A线程的+1操作已经完成,准备写入,所以此时A没有阻塞之后,继续进行写入操作,最终 i 的值就是A写入的值,即11,所以没有保证原子性,出现了线程不安全问题。

在这里插入图片描述

那么到底要怎么解决呢?

就是使用我一开始提到的 AtomicInteger 解决这类问题,代码如下:

在这里插入图片描述

可以看到结果是正确的,为此我还专门 调大了线程数和每个线程的i++次数,可结果仍然正确。

但这些只是前戏

在这里插入图片描述


什么是CAS?

CAS中文翻译就是 比较并交换(Compare And Swap)

CAS操作包括了3个操作数:

  1. 需要读写的内存位置(V)
  2. 进行比较的预期值(A)
  3. 拟写入的新值(B)

当且仅当如果内存位置V的值等于预期的A值,则将该位置更新为新值B,否则不进行任何操作。许多CAS的操作是自旋的:如果操作不成功,会一直重试,直到操作成功为止。

但是既然涉及到了 交换 ,那么怎么保证原子性呢 ?

答案:CAS是由CPU支持的原子操作,其原子性是在硬件层面进行保证的。


CAS原理

那么今天的重点就是:CAS的实现原理,而AtomicInteger底层就是使用到的CAS。

public class Main { 
   


    public static void main(String[] args) throws InterruptedException { 
   

        AtomicInteger num = new AtomicInteger(0);
        num.getAndIncrement();
    }

}

在这里插入图片描述
在这里插入图片描述

而 compareAndSwapInt 是个 native方法
在这里插入图片描述

可以看见 getAndIncrement() 方法返回的是 unsafe.getAndAddInt(this, valueOffset, 1);

那么,这个 this就是当前的 AtomicInteger 对象,而valueOffset 是当前 AtomicInteger对象 value值的内存偏移量,而1 是每次需要增加的值。

面试被问到CAS原理,触及知识盲区,脸都绿了!

那么继续往下走就是 getAndAddInt() 方法

根据 一 一对应关系

  • var1 = this
  • var2 = valueOffset
  • var4 = 1

而 this.getIntVolatile(var1, var2); 可以理解为获取 var1对象对应的内存偏移量var2对应的值,赋值给 var5

最后是 compareAndSwapInt(var1, var2, var5, var5 + var4),即理解为如果当前 var1对象对应的var4内存偏移量所对应的值与var5相等的话,就对值进行更新。

    public final int getAndAddInt(Object var1, long var2, int var4) { 
   
        int var5;
        do { 
   
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

完犊子,不知不觉把 AtomicInteger 原理也讲了,这不就少写一篇博文,?


既然讲了,那就小总结一下吧:

AtomicInteger实现原理简单来说就是 Unsafe类 加 自旋(do while循环),如果在详细点就说 CAS嘛。

也可以说AtomicInteger是 CAS的一个使用场景,不仅是AtomicInteger,还有各种Atomic开头的原子类,内部都应用到了CAS。

而且上文中涉及到的 Unsafe类是 CAS的核心类,因为Java方法无法访问底层系统,所以需要通过 上文中的 compareAndSwapInt 这样的 native方法来进行访问Unsafe类存在于sun.misc包中,提供硬件级别的原子操作。

在这里插入图片描述


CAS带来的问题

  • ABA问题 解决办法 → 如何解决 CAS 的 ABA问题
  • 如果自旋长时间不成功,那么循环开销时间大
  • 只能保证一个共享变量的操作 → 可以使用 AtomicReference 解决

本篇博文到此页也结束了

等等

读者大大:好歹总结一下CAS原理吧

皮皮虾:时刻为您效劳,只要点赞什么都好说(手动滑稽)


CAS原理总结,面试问到了到底该怎么说?

答:CAS是一种乐观锁机制具体实现为 Unsafe类 + 自旋,通过Unsafe类提供硬件级别的原子性操作保证了并发安全,加上自旋操作,解决了 synchronized 在多线程环境下会出现的线程阻塞,唤醒切换,以及用户态内核态间的切换操作所带来的消耗。在由此扩展到 AtomicInteger也就是我上面讲的一套说给面试官,绝对 OK!


尾言

我是 Code皮皮虾,一个热爱分享知识的 皮皮虾爱好者,未来的日子里会不断更新出对大家有益的博文,期待大家的关注!!!

创作不易,如果这篇博文对各位有帮助,希望各位小伙伴可以一键三连哦!,感谢支持,我们下次再见~~~

分享大纲

大厂面试题专栏

Java从入门到入坟学习路线目录索引

开源爬虫实例教程目录索引

更多精彩内容分享,请点击 Hello World (●’◡’●)


在这里插入图片描述

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/181155.html原文链接:https://javaforall.net

(0)
全栈程序员-站长的头像全栈程序员-站长


相关推荐

  • MUX VLAN详解与配置实例「建议收藏」

    MUX VLAN详解与配置实例「建议收藏」今天给大家介绍MUIXVLAN的相关理论知识,同时使用华为eNSP模拟器,完成了企业网中常见的MUXVLAN配置。一、MUXVLAN原理MUXVLAN是一种VLAN控制层面的访问控制技术,使用MUXVLAN,可以实现VLAN间的流量隔离,并且比较灵活的提供了多种实现方式。MUXVLAN存在主VLAN(SubordinateVLAN)、隔离性VLAN(SeparateVLAN)、**互通性VLAN(GroupVLAN)**三种类型的VLAN。其中隔离性VLAN和互通型VLAN又被称为从

    2022年9月18日
    1
  • 《数据库系统概论》之数据库设计六步骤(需求、概念、逻辑、物理、实施、运行维护)

    《数据库系统概论》之数据库设计六步骤(需求、概念、逻辑、物理、实施、运行维护)文章目录0.一图总览1.数据库设计概述及六步骤简介2.需求分析—步骤一2.1收集资料2.2分析整理2.3数据流图2.4数据字典2.5用户确认3.概念结构设计—步骤二3.1E-R模型3.2建立E-R模型4.逻辑结构设计—步骤三4.1E-R模型向关系模式的转换4.2关系模式的优化4.3设计用户子模式5.物理结构设计—步骤四5.1确定数据库的物理结构5.2物理结构进行评价6.数据库实施—步骤五7.数据库运行维护—步骤六0.一图总览1.数据库设计概述及

    2022年10月7日
    0
  • 姿态估计与行为识别(行为检测、行为分类)的区别[通俗易懂]

    姿态估计与行为识别(行为检测、行为分类)的区别[通俗易懂]姿态估计和行为识别作为计算机视觉的两个领域,对于新人来说,较为容易弄混姿态估计和行为识别两个概念。 姿态估计(PoseEstimation)是指检测图像和视频中的人物形象的计算机视觉技术,可以确定某人的某个身体部位出现在图像中的位置,也就是在图像和视频中对人体关节的定位问题,也可以理解为在所有关节姿势的空间中搜索特定姿势。简言之,姿态估计的任务就是重建人的关节和肢干,其难点主要在于…

    2022年6月21日
    23
  • 基于Packet Tracer的校园网络设计与规划「建议收藏」

    基于Packet Tracer的校园网络设计与规划「建议收藏」本文仅仅为本人课程要求而设计的方案,本文仅仅使用RIP动态路由协议,HSRP协议,浮动路由,静态路由,等价路由,DHCP地址分配,NAT技术,VLAN划分。如有问题请大佬们在本博客下留言。一、设计要求模拟设计并实现校园网规划,完成拓扑结构设计、IP地址规划、路由协议、网络管理规划、安全管理规划,用软件模拟器实现此规划。选取设备和协议根据你对校园网的了解和调研情况确定,也可以进行适当程度的假设…

    2022年10月5日
    0
  • linux卸载wine qq,ubuntu安装wineQQ

    linux卸载wine qq,ubuntu安装wineQQUbuntu系发行版安装deepinwineQQ的步骤第1步,安装deepin-wine环境:上https://github.com/wszqkzqk/deepin-wine-ubuntu页面下载zip包(或用git方式克隆),解压到本地文件夹,在文件夹中打开终端,输入sudosh./install.sh一键安装。一些小问题的解决方法0,安装之后找不到在哪里启动:安装完deepin.com…

    2022年9月6日
    3
  • 图形的遍历

    图形的遍历一个图形G=(V,E),存在某一顶点v,希望从v开始,通过此顶点相邻的顶点而去访问G中其他顶点直达全部的顶点遍历完毕。在遍历的过程中可能会重复经过某些顶点及边线,经由图形的遍历可以判断该图形是否连通,并找出连通单元和路径。图形遍历有两种方法:深度优先搜索Deep-First-Search广度优先搜索Breadth-First-Search一、深度优先搜索从图形的某一顶点开始遍历,被访问过的

    2022年6月8日
    47

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号