CAS原理、实践、缺陷分析及优化

news2024/11/15 12:30:45

文章目录

      • CAS介绍
      • CAS在Java中的底层实现
      • Java源码中使用举例
      • 自己实际运用乐观锁举例
        • 简单的CAS操作
        • ABA问题及优化实践
      • 缺陷及优化
        • 只能保证单个变量操作原子性
        • 当比较失败后,通常需要不断重试,浪费CPU

CAS介绍

CAS(Compare And swap),比较并交换,是一种乐观锁.它是解决多线程并行情况下使用悲观锁造成性能损耗的一种机制.

CAS 是实现乐观锁的核心算法,它包含了 3 个参数:V(需要更新的变量)、E(预期值)和 N(最新值)。只有当需要更新的变量等于预期值时,需要更新的变量才会被设置为最新值,如果更新值和预期值不同,则说明已经有其它线程更新了需要更新的变量,此时当前线程不做操作,返回失败

乐观锁相比悲观锁来说,不会带来死锁、饥饿等活性故障问题,线程间的相互影响也远远比悲观锁要小。更为重要的是,乐观锁没有因竞争造成的系统开销,所以在性能上也是更胜一筹。

CAS在Java中的底层实现

CAS在Java中的最底层实现其实都封装在Unsafe(sun.misc.Unsafe)类中.
主要提供了三个比较并交换的方法.
在这里插入图片描述

为什么叫Unsafe类呢?
因为在Java中,基本没有直接的指针组件,一般也不能使用偏移量对某块内存进行操作。这些操作相对来讲是安全(safe)的。

而这个Unsafe类使 Java 拥有了像 C 语言的指针一样直接操作内存空间的能力,可用来直接访问系统内存资源并进行自主管理,是Java并发的基础.

但是同时也带来了指针的问题。即一个新手在编程时,没有考虑指针的安全性,错误的操作指针把某块不该修改的内存值修改,容易导致整个程序崩溃。

所以官方并不建议我们直接使用Unsafe类.

Java源码中使用举例

  1. AQS中加锁操作
    在这里插入图片描述
  2. ConcurrentHashMap中加锁操作.
    在这里插入图片描述
  3. atomic 路径下的所有类.
    在这里插入图片描述
    这些原子类底层其实都是运用的CAS操作,调用Unsafe类中的方法.如:
    在这里插入图片描述

自己实际运用乐观锁举例

如果我们想自己在项目中运用乐观锁,应该怎样去用呢?

简单的CAS操作

我们可以直接使用Atomic路径下封装好的CAS方法.

如果是数值类型的我们可以直接用AtomicInteger、AtomicLong这些类
如果是其他类型我们可以用AtomicReference,它可以传入一个泛型,从而进行一个复杂对象的比较并替换,这里举例假如比较的是Char类型.

在这里插入图片描述

该方法会返回更新成功或者失败,那如果更新失败,我们应该怎样做?
这里就要区分不同的业务场景了,通常来说有两种情况.

  1. 直接返回失败,返回一个友好提示由上层进行处理
  2. 获取当前最新值,然后重新计算更新值,不断进行重试.

ABA问题及优化实践

这里还有一个问题,就是CAS常见的ABA问题,

CAS 算法是基于值来做比较的,如果当前有两个线程,一个线程将变量值从 A 改为 B ,再由 B 改回为 A ,当前线程开始执行 CAS 算法时,就很容易认为值没有变化,误认为读取数据到执行 CAS 算法的期间,没有线程修改过数据。

解决这个问题的常见方法就是增加版本号或者时间戳作为额外验证手段,确保在当前期间没有其他线程对其做过操作.

在Java中也有原子类AtomicStampedReference实现了这个功能,我们直接调用方法即可.

    volatile int value=0; //初始值
    volatile int stamp=0; //初始版本号

    public void test(){
        //初始化对象,传入初始值和初始的版本号
        AtomicStampedReference<Integer> stampedReference = new AtomicStampedReference<Integer>(value,stamp);

        //使用比较并交换进行CAS操作
        //四个参数分别是初始值,更新值,初始版本,更新版本
        boolean result = stampedReference.compareAndSet(0, 5, 0, 1);
        System.out.println(result);

        //获取最新值
        System.out.println(stampedReference.getReference());

        //获取最新版本
        System.out.println(stampedReference.getStamp());
    }

缺陷及优化

只能保证单个变量操作原子性

CAS 乐观锁在平常使用时比较受限,它只能保证单个变量操作的原子性,当涉及到多个变量时,CAS 就无能为力了,这个时候还是需要悲观锁,通过对整个代码块加锁来做到这点。

目前使用乐观锁最常见的场景就是数据库的更新操作了。为了保证操作数据库的原子性,我们常常会为每一条数据定义一个版本号,并在更新前获取到它,到了更新数据库的时候,还要判断下已经获取的版本号是否被更新过,如果没有,则执行该操作

当比较失败后,通常需要不断重试,浪费CPU

CAS 乐观锁在高并发写大于读的场景下,大部分线程的原子操作会失败,失败后的线程将会不断重试 CAS 原子操作,比如说 AtomicInteger 和 AtomicLong等都是这样的.这样就会导致大量线程长时间地占用 CPU 资源,给系统带来很大的性能开销。

在 JDK1.8 中,Java 提供了一个新的原子类 LongAdder。LongAdder 在高并发场景下会比 AtomicInteger 和 AtomicLong 的性能更好,代价就是会消耗更多的内存空间

LongAdder 的原理就是降低操作共享变量的并发数,也就是将对单一共享变量的操作压力分散到多个变量值上,将竞争的每个写线程的 value 值分散到一个数组中,不同线程会命中到数组的不同槽中,各个线程只对自己槽中的 value 值进行 CAS 操作,最后在读取值的时候会将原子操作的共享变量与各个分散在数组的 value 值相加,返回一个近似准确的数值。

这里返回一个近似准确的数值要怎么理解呢? 再说的具体一点,假设操作后立即要获取到值,这个值可能是一个不准确的值。如果我们等待所有线程执行完成之后去获取,这个值肯定是准确的值。一般在做统计时,会经常用到这种操作,实时展现的只要求一个近似值,但最终的统计要求是准确的,这个时候可以使用LongAdder。而在一些对实时性要求比较高的场景下,LongAdder 并不能取代 AtomicInteger 或 AtomicLong

下面是LongAdder用法举例及和AtomicLong的性能对比.

分别使用这两个类对数值累加300w次.

public class Test {

    public static void main(String[] args) {
        calculateTime(new AtomicLong(), 300);
        calculateTime(new LongAdder(), 300);
    }

    public static void calculateTime(Number count, int threadNum){
        CountDownLatch countDownLatch = new CountDownLatch(threadNum);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < threadNum; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    count(count);
                }
                countDownLatch.countDown();
            }).start();
        }
        try {
            countDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("最终获得结果:"+get(count));
        System.out.println("消耗时间:"+(System.currentTimeMillis() - startTime));
    }




    public static void count(Number count){
        if(count instanceof LongAdder){
            ((LongAdder) count).increment();
        }else if(count instanceof AtomicLong){
            ((AtomicLong) count).addAndGet(1);
        }else {
            throw new RuntimeException("其他实现类");
        }
    }

    public static Long get(Number count){
        if(count instanceof LongAdder){
            return ((LongAdder) count).sum();
        }
        if(count instanceof AtomicLong){
            return ((AtomicLong) count).get();
        }
        throw new RuntimeException("其他实现类");
    }
}

运行结果:
在这里插入图片描述

今天的分享就到这里了,有问题可以在评论区留言,均会及时回复呀.
我是bling,未来不会太差,只要我们不要太懒就行, 咱们下期见.
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/134656.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【力扣Java】第一个出现两次的字母(HashSet与<<左移)

目录 题目描述 思路与算法 方法一&#xff1a;哈希表 HashSet说明 解题代码 方法二&#xff1a;位运算 左移运算符&#xff08;<<&#xff09; 右移运算符&#xff08;>>&#xff09; 解题代码 题目描述 给你一个由小写英文字母组成的字符串 s &#xff…

事务四大特性

没有开启事务的话直接提交、修改数据&#xff0c;有事务则进行两阶段提交&#xff1a; ①原子性 undolog 来实现一旦失败就马上触发回滚 当前事务是不可再分的&#xff0c;要么一起执行&#xff0c;要么都不执行。 start transaction redolog日志池存储undolog日志&#xff…

【Android OpenCV】Visual Studio 创建支持 OpenCV 库的 CMake 工程 ④ ( OpenCV 官方代码示例 )

文章目录一、OpenCV 官方代码示例1、Windows 平台代码示例2、Android 平台代码示例参考 【Android OpenCV】Visual Studio 创建支持 OpenCV 库的 CMake 工程 ① ( 下载 OpenCV 库 | Windows 中安装 OpenCV 库 ) 博客 , 可下载 OpenCV 库 ; CSDN 下载地址 : https://download.c…

educoder数据结构 字符串匹配 第2关:实现KMP字符串匹配

本文已收录于专栏 &#x1f332;《educoder数据结构与算法_大耳朵宋宋的博客-CSDN博客》&#x1f332; 任务描述 本关的编程任务是补全 step2/kmp.cpp 文件中的KmpGenNext函数&#xff0c;以实现 KMP 字符串匹配。该函数生成给定字符串的next数组。 相关知识 第 1 关中实现…

LEETCODE 19. 删除链表的倒数第 N 个结点

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5], n 2 输出&#xff1a;[1,2,3,5]示例 2&#xff1a; 输入&#xff1a;head [1], n 1 输出&#xff1a;[]示例 3&#xff1a;…

shell流程控制之条件判断练习案列

练习案例1、判断当前磁盘剩余空间是否有20G&#xff0c;如果小于20G&#xff0c;则将报警邮件发送给管理员&#xff0c;每天检查一次磁盘剩余空间。2、判断web服务是否运行&#xff08;1、查看进程的方式判断该程序是否运行&#xff0c;2、通过查看端口的方式判断该程序是否运行…

memcpy与memmove函数的区别和实现

1.函数定义 memcpy与memmove都是C语言的库函数&#xff0c;在头文件string.h中&#xff0c;作用是内存拷贝。唯一的区别是&#xff0c;当内存发生局部重叠时&#xff0c;memmove保证了拷贝的结果是正确的&#xff0c;但是memcopy不一定是正确的。但是memcpy比memmove速度快。 …

zookeeper学习笔记3(小滴课堂)

zk集群核心知识之三种角色及其作用&#xff1a; 注册中心的三种模式&#xff1a; 选举核心概念及选举状态&#xff1a; 选举发生的时机及选举算法&#xff1a; zookeeper集群的搭建&#xff1a; 我们先来启动三台虚拟机&#xff1a; 然后我们来修改一下我们的zookeeper配置&a…

Introducing Tome, AI讲演助手

随着ChatGPT进入人们的视野&#xff0c;AI开始在越来越多的领域大展拳脚&#xff0c;近期&#xff0c;一款名为Tome的讲演编辑工具&#xff08;类似幻灯片&#xff09;推出了AI辅助创作的功能&#xff0c; Tome的AI讲演功能可以更轻松地将文本、图像、音频等多种媒体元素融合到…

CPU缓存一致性协议MESI

目录 CPU高速缓存&#xff08;Cache Memory&#xff09; CPU为何要有高速缓存 带有高速缓存的CPU执行计算的流程 目前流行的多级缓存结构 多核CPU多级缓存一致性协议MESI MESI协议缓存状态 MESI状态转换 多核缓存协同操作 单核读取 双核读取 修改数据 同步数据…

“英雄无敌”(一)

注意&#xff1a;在阅读本文前建议观看对应的视频教程&#xff0c;本文主要作为那些对视频教程仍有疑问的学习者的参考资料。且本文的部分内容有些过时&#xff08;如仍使用旧版Unity的动画系统&#xff09;&#xff0c;故直接阅读本文对Unity的学习没有太大意义 视频教程&…

写小论文心得(计算机视觉领域)

简介 这篇博文打算讲一下我写小论文前后的过程&#xff0c;其中有许多地方我觉得对于新手来说还是有很多帮助的&#xff0c;包括我自己也是自己慢慢摸索出来的&#xff0c;希望能对正在准备自己第一篇论文的人有所帮助。 写论文的整个过程我想分为几个阶段&#xff1a; 1.获得…

【深度学习】生成对抗网络(GAN)生成海贼王图像 + PyTorch代码实现

文章目录一、数据集介绍1.1 lufei&#xff08;路飞&#xff09;1.2 luobin&#xff08;罗宾&#xff09;1.3 namei&#xff08;娜美&#xff09;1.4 qiaoba&#xff08;乔巴&#xff09;1.5 shanzhi&#xff08;山智&#xff09;1.6 suolong&#xff08;索隆&#xff09;1.7 wu…

【内核笔记】2022回顾-2023规划

文章目录一、2022博客总结二、2023博客规划三、改进沉淀、分享、成长&#xff0c;让自己和他人都能有所收获&#xff01;&#x1f604; 一、2022博客总结 2022年重点优化了&#x1f680;RK3399平台开发入门到精通系列专栏&#xff0c;订阅量突破2100个订阅2022年浏览量破190万2…

《梁启超家书》笔记三——交友取益,或读书取益,也要方面稍多,才有接谈交换,或开卷引进的机会

目录 1、学科选择建议 2、学习 3、所学不可太专向&#xff08;不知道学科外其他方向的了&#xff09; 4、建议欧游实地开开眼界 5、建议结婚前分开两地住 6、毕业后工作看法(分析利弊) 7、处事态度与人生观 1、学科选择建议 我很想你以生物学为主科&#xff0c;因为它是…

Vue(五)

1. 绑定class和style样式 dom里自己拿到节点&#xff0c;改它上面的class样式 shift()移除第一个class属性 push想加哪个class属性都行 59行div知道自己只能用两个样式&#xff0c;四种情况 上面这种不合法&#xff0c;就算把样式加进div也得 样式对象&#xff1a;里面的key绿…

【ElasticSearch7.X】学习笔记(一)

【ElasticSearch7.X】学习笔记一、介绍1.1、ElasticSearch1.2、RESTful1.3、数据格式二、下载安装&#xff08;单机部署&#xff09;2.1、解压文件2.2、修改配置配置2.3、创建用户并授权2.5、启动三、增删查改3.1、 索引操作3.1.1、增加索引3.1.2、查看索引3.1.2.1、查看所有索…

定时任务介绍及X-Job和E-Job简单对比

下面对elastic-Job和xxl-Job做个简单的描述和对比 一、区别 1、XJob环境依赖于mysql&#xff0c;不依赖ZooKeeper&#xff0c; 2、EJob依赖ZooKeeper1、X-Job 侧重的业务实现的简单和管理的方便&#xff0c;学习成本简单&#xff0c; 失败策略和路由策略丰富。推荐使用在“用户…

对elasticsearch的实际应用

有业务需求&#xff0c;要做一个搜索接口&#xff0c;从千万的数据中根据筛选项查到需要的数据 筛选项有 1.学段。高中&#xff0c;初中等 2.学科。数学&#xff0c;语文等 3.专区。同步专区&#xff0c;高考专区等 4.试卷类型。月考卷&#xff0c;期中考试卷等 5.年级。高一&a…

MyBatis-Plus常用注解

常用注解 TableName 在使用MyBatis-Plus实现基本的CRUD时&#xff0c;我们并没有指定要操作的表&#xff0c;只是在Mapper接口继承BaseMapper时&#xff0c;设置了泛型User&#xff0c;而操作的表为user表 由此得出结论&#xff0c;MyBatis-Plus在确定操作的表时&#xff0c;…