CAS

本文涉及的产品
函数计算FC,每月15万CU 3个月
应用实时监控服务-可观测链路OpenTelemetry版,每月50GB免费额度
应用实时监控服务-应用监控,每月50GB免费额度
简介: 本文主要讲解java中cas的概念及原理

compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。

它包含三个操作数一一内存位置的值、预期原值及更新值。

执行CAS操作的时候,将内存位置的值与预期原值比较。

如果相匹配,,那么处理器会自动将该位置值更新为新值。

如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功

image.png

CAS是JDK提供的非阻塞原子性操作,它通过硬件保证了比较-更新的原子性。

它是非阻塞的且自身具有原子性,也就是说这玩意效率更高且通过硬件保证,说明这玩意更可靠。

CAS是一条CPU的原子指令 (cmpxchg指令),不会造成所谓的数据不一致问题,Unsafe提供的CAS方法(如compareAndSwapXXX) 底层实现即为CPU指令cmpxchg。执行cmpxchg指令的时候,会判断当前系统是否为多核系统,如果是就给总线加锁,只有一个线程会对总线加锁成功,加锁成功之后会执行cas操作,也就是说CAS的原子性实际上是CPU实现独占的,比起用synchronized重量级锁, 这里的排他时间要短很多, 所以在多线程情况下性能会比较好。

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

原子引用

public class AtomicReferenceDemo {

   public static void main(String[] args) {

       AtomicReference<User> atomicReference=new AtomicReference<>();

       User z3=new User("z3",22);

       User l4=new User("l4",28);

       atomicReference.set(z3);

       System.out.println(atomicReference.compareAndSet(z3,l4)+"\t"+atomicReference.get().toString());//true  User(userName=l4, age=28)

       System.out.println(atomicReference.compareAndSet(z3,l4)+"\t"+atomicReference.get().toString());//false User(userName=l4, age=28)

   }

}

@Getter

   @ToString

   @AllArgsConstructor

   class User{

       String userName;

       int age;

   }

自旋锁

CAS 是实现自旋锁的基础,CAS 利用 CPU 指令保证了操作的原子性,以达到锁的效果,至于自旋呢,看字面意思也很明白,自己旋转。是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

/*

通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒钟,B随后进来后发现

当前有线程持有锁,所以只能通过自旋等待,知道A 释放锁B抢到

*/

public class SpinLockDemo {

   AtomicReference<Thread> atomicReference=new AtomicReference<>();

   public void lock(){

       Thread thread = Thread.currentThread();

       System.out.println(Thread.currentThread().getName()+"\t"+"-----come in");

       while (!atomicReference.compareAndSet(null,thread)){

       }

   }

   public void unlock(){

       Thread thread = Thread.currentThread();

       atomicReference.compareAndSet(thread,null);

       System.out.println(Thread.currentThread().getName()+"\t"+"-----task over");

   }

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

       SpinLockDemo spinLockDemo = new SpinLockDemo();

       new Thread(()->{

           spinLockDemo.lock();

           try {

               TimeUnit.SECONDS.sleep(5);

           } catch (InterruptedException e) {

               throw new RuntimeException(e);

           }

           spinLockDemo.unlock();

       },"a").start();

       //线程a先与b启动

       TimeUnit.MILLISECONDS.sleep(100);

       new Thread(()->{

           spinLockDemo.lock();

           spinLockDemo.unlock();

       },"b").start();

   }

}

CAS的缺点

循环时间长开销很大。

ABA问题:

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。

比如说一个线程1从内存位置V中取出A,这时候另一个线程2也从内存中取出A,并且线程2进行了一些操作将值变成了B。然后线程2又将V位置的数据变成A,这时候线程1进行CAS操作发现内存中仍然是A,预期OK,然后线程1操作成功。

尽管线程1的CAS操作成功,但是不代表这个过程就是没有问题的。

ABA问题解决

单线程

public class AtomicStampedDemo {

   public static void main(String[] args) {

       Book book=new Book(1,"javaBook");

       AtomicStampedReference<Book> stampedReference=new AtomicStampedReference<>(book,1);

       System.out.println( stampedReference.getReference()+"\t"+stampedReference.getStamp());

       //Book(id=1, bookName=javaBook)    1

       Book mysql=new Book(1,"mysqlBook");

       boolean flag;

       flag = stampedReference.compareAndSet(book, mysql, stampedReference.getStamp(), stampedReference.getStamp() + 1);

       System.out.println( flag+"  "+stampedReference.getReference()+"\t"+stampedReference.getStamp());

       //true  Book(id=1, bookName=mysqlBook) 2

       flag = stampedReference.compareAndSet(mysql, book, stampedReference.getStamp(), stampedReference.getStamp() + 1);

       System.out.println( flag+"  "+stampedReference.getReference()+"\t"+stampedReference.getStamp());

       //true  Book(id=1, bookName=javaBook)  3

   }

}

@Data

   @AllArgsConstructor

   @NoArgsConstructor

   class Book{

       private int id;

       private String bookName;

   }

多线程下发生的问题

public class ABCDemo {

   static AtomicInteger atomicInteger=new AtomicInteger(100);

   public static void main(String[] args) {

       new Thread(()->{

           atomicInteger.compareAndSet(100,101);

           try {

               TimeUnit.MILLISECONDS.sleep(10);

           } catch (InterruptedException e) {

               throw new RuntimeException(e);

           }

           atomicInteger.compareAndSet(101,100);

       },"t1").start();

       new Thread(()->{

           try {

               TimeUnit.MILLISECONDS.sleep(200);

           } catch (InterruptedException e) {

               throw new RuntimeException(e);

           }

           System.out.println(atomicInteger.compareAndSet(100,2022)+"\t"+atomicInteger.get());

       },"t2").start();

   }

}

//true 2022

多线程解决ABA

static AtomicInteger atomicInteger=new AtomicInteger(100);

static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1);

public static void main(String[] args) {

   new Thread(()->{

       int state=stampedReference.getStamp();

       System.out.println(Thread.currentThread().getName()+"\t首次版本号为"+stampedReference.getStamp());

       //t3   首次版本号为1

       try {

           TimeUnit.MILLISECONDS.sleep(500);

       } catch (InterruptedException e) {

           throw new RuntimeException(e);

       }

       stampedReference.compareAndSet

           (100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);

       System.out.println(Thread.currentThread().getName()+"\t二次版本号为"+stampedReference.getStamp());

       //t3   二次版本号为2

       stampedReference.compareAndSet

           (101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);

       System.out.println(Thread.currentThread().getName()+"\t三次版本号为"+stampedReference.getStamp());

       //t3   三次版本号为3

   },"t3").start();

   new Thread(()->{

       int state=stampedReference.getStamp();

       System.out.println(Thread.currentThread().getName()+"\t首次版本号为"+state);

       //t4   首次版本号为1

       //等待t3发生过ABA

       try {

           TimeUnit.SECONDS.sleep(1);

       } catch (InterruptedException e) {

           throw new RuntimeException(e);

       }

       boolean flag = stampedReference.compareAndSet

           (100, 2022, state, state + 1);

       System.out.println(flag+"\t"+stampedReference.getReference()+"\t"+stampedReference.getStamp());

       //false    100    3

   },"t4").start();

}

相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
目录
相关文章
|
8月前
|
数据格式 Python
如何处理用户输入数据格式验证不通过的情况?
如何处理用户输入数据格式验证不通过的情况?
336 79
|
1月前
|
Java 关系型数据库 数据库
深度剖析【Spring】事务:万字详解,彻底掌握传播机制与事务原理
在Java开发中,Spring框架通过事务管理机制,帮我们轻松实现了这种“承诺”。它不仅封装了底层复杂的事务控制逻辑(比如手动开启、提交、回滚事务),还提供了灵活的配置方式,让开发者能专注于业务逻辑,而不用纠结于事务细节。
|
2月前
|
机器学习/深度学习 算法 安全
深度长文I 深度合成服务类-算法备案该怎么做?
本文详解“深度合成服务类”算法及其备案要求,涵盖定义、类型、备案流程等内容,助你全面理解合规要点。
|
JavaScript 前端开发 程序员
Vue学习之--------Vue生命周期beforeCreate、created、beforeMount、mounted、beforeDestroy 。。。(图解详细过程)(2022/7/17)
这篇文章详细介绍了Vue的生命周期和各个阶段的钩子函数,包括`beforeCreate`、`created`、`beforeMount`、`mounted`、`beforeUpdate`、`updated`、`beforeDestroy`和`destroyed`。文章通过图解、方法说明、代码实例和测试效果,阐述了每个钩子函数的作用和使用场景,帮助读者深入理解Vue实例从创建到销毁的整个过程。
Vue学习之--------Vue生命周期beforeCreate、created、beforeMount、mounted、beforeDestroy 。。。(图解详细过程)(2022/7/17)
|
存储 缓存 Kubernetes
在K8S中,业务Pod数据如何存储?
在K8S中,业务Pod数据如何存储?
|
前端开发 JavaScript Java
前后端分离|基于Springboot+Vue校园闲置物品交易系统设计与实现
前后端分离|基于Springboot+Vue校园闲置物品交易系统设计与实现
192 0
前后端分离|基于Springboot+Vue校园闲置物品交易系统设计与实现
|
缓存 Java 应用服务中间件
Docker 镜像构建的最佳做法
Docker 镜像构建的最佳做法
232 1
|
Shell Linux C++
报错:CommandNotFoundError: Your shell has not been properly configured to use ‘conda activate‘. 问题解决
报错:CommandNotFoundError: Your shell has not been properly configured to use ‘conda activate‘. 问题解决
627 0
|
资源调度 Ubuntu 前端开发
使用verdaccio+docker搭建 npm 私有仓库以及使用
公司内部前端组件或库的共享等,搭建一个`npm`私有库就很方便,现在中大型公司也基本都有自己的`npm`私有库,这篇文章,和大家一起共同搭建一个`npm`私有库,共同学习
904 0
|
开发框架 .NET 数据中心
IDC数据中心是什么
IDC,数据中心
1923 0