CAS

本文涉及的产品
容器镜像服务 ACR,镜像仓库100个 不限时长
应用实时监控服务-可观测链路OpenTelemetry版,每月50GB免费额度
云原生网关 MSE Higress,422元/月
简介: 本文主要讲解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();

}

相关实践学习
如何快速连接云数据库RDS MySQL
本场景介绍如何通过阿里云数据管理服务DMS快速连接云数据库RDS MySQL,然后进行数据表的CRUD操作。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
存储 资源调度 安全
H3C CAS系列 一、CAS初认识
对于虚拟化,可能第一时间大家想到的是虚拟机,而对于虚拟机大家可能第一时间想到的就是我们大多数人都可能比较熟悉的VMware系列产品,比如常用VMware Workstation Pro 、VMware esxi。 而今天我带大家一起认识一款我们国产的虚拟化软件 H3C CAS。
1790 0
|
7月前
|
算法 调度 数据安全/隐私保护
什么是CAS锁
什么是CAS锁
95 0
|
7月前
|
Java API
CAS的超~详细介绍
CAS的超~详细介绍
|
7月前
|
存储 算法 Java
|
7月前
|
算法
原子操作CAS
原子操作CAS
46 0
|
安全
CAS和多线程密切相关的东西!
CAS和多线程密切相关的东西!
34 0
什么是 CAS? CAS 有哪些缺点?ABA 问题是什么?
什么是 CAS? CAS 有哪些缺点?ABA 问题是什么?
212 0
|
算法 安全 Java
简单理解CAS
简单理解CAS
279 0
简单理解CAS