Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >CAS底层原理(cas理论模型)

CAS底层原理(cas理论模型)

作者头像
全栈程序员站长
发布于 2022-07-29 13:43:48
发布于 2022-07-29 13:43:48
1.2K00
代码可运行
举报
运行总次数:0
代码可运行

大家好,又见面了,我是你们的朋友全栈君。

一、什么是CAS

CAS的全称为Compare-And-Swap ,它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更新为新的值,这个过程是原子的。

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

在Java发展初期,java语言是不能够利用硬件提供的这些便利来提升系统的性能的。而随着java不断的发展,Java本地方法(JNI或JNA)的出现,使得java程序越过JVM直接调用本地方法提供了一种便捷的方式,因而java在并发的手段上也多了起来。而在Doug Lea提供的cucurenct包中,CAS理论是它实现整个java包的基石。

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

通常将 CAS 用于同步的方式是从地址 V 读取值 A,执行多步计算来获得新 值 B,然后使用 CAS 将 V 的值从 A 改为 B。如果 V 处的值尚未同时更改,则 CAS 操作成功。

类似于 CAS 的指令允许算法执行读-修改-写操作,而无需害怕其他线程同时 修改变量,因为如果其他线程修改变量,那么 CAS 会检测它(并失败),算法 可以对该操作重新计算。

1.1、Demo1 — atomicInteger类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * Description
 * @date 2019-04-12 9:57
 * 1.什么是CAS ? ===> compareAndSet
 *  比较并交换
 **/
public class CASDemo {
    public static void main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(5);
        System.out.println(atomicInteger.compareAndSet(5, 2019)+"\t current"+atomicInteger.get());
        System.out.println(atomicInteger.compareAndSet(5, 2014)+"\t current"+atomicInteger.get());
    }
}

1.2、Demo2 — 原子引用类

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
@Getter@Setter@AllArgsConstructor@ToString
class User{
    private String name;
    private int age;
}
public class AtomicReferenceDemo {
    public static void main(String[] args) {
        User zs = new User("zs", 22);
        User ls = new User("ls", 22);
        AtomicReference<User> userAtomicReference = new AtomicReference<>();

        userAtomicReference.set(zs);

        // 第一次比较交换成功,因为主内存中的值跟期望值一致(zs == zs)

        System.out.println(userAtomicReference.compareAndSet(zs,ls)+"\t"+userAtomicReference.get().toString());

        // 第二次比较交换失败,因为主内存中的值跟期望值不一致(ls != zs)

        System.out.println(userAtomicReference.compareAndSet(zs,ls)+"\t"+userAtomicReference.get().toString());
    }
}

1.3、sun.miscUnSafe类

UnSafe类是CAS的核心类由于Java 方法无法直接访问底层,需要通过本地(native)方法来访问,基于该类可以直接操作特额定的内存数据.UnSafe类在于sun.misc包中,其内部方法操作可以向C的指针一样直接操作内存,因为Java中CAS操作的助兴依赖于UNSafe类的方法。

  • 变量ValueOffset:它是该变量在内存中的偏移地址,因为UnSafe就是根据内存偏移地址获取数据的
  • 变量value:被volatile修饰,保证了多线程之间的可见性.

注意:UnSafe类中所有的方法都是native修饰的,也就是说UnSafe类中的方法都是直接调用操作底层资源执行响应的任务。

二、CAS的目的

利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。而整个J.U.C都是建立在CAS之上的,因此相比synchronized阻塞算法,J.U.C在性能上有了很大的提升。

三、CAS存在的问题

虽然很高效的解决原子操作,但是CAS仍然存在三大问题。

3.1、ABA问题

问题描述:当你获得对象当前数据后,在准备修改为新值前,对象的值被其他线程连续修改了两次,而经过两次修改后,对象的值又恢复为旧值,这样当前线程无法正确判断这个对象是否修改过。 解决办法:JDK1.5可以利用AtomicStampedReference类来解决这个问题,AtomicStampedReference内部不仅维护了对象值,还维护了一个时间戳。当AtomicStampedReference对应的数值被修改时,除了更新数据本身外,还必须要更新时间戳,对象值和时间戳都必须满足期望值,写入才会成功

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
/**
 * Description: ABA问题的解决
 *
 * @author veliger@163.com
 * @date 2019-04-12 21:30
 **/
public class ABADemo {
    private static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);
    private static AtomicStampedReference<Integer> stampedReference=new AtomicStampedReference<>(100,1);
    public static void main(String[] args) {
        System.out.println("===以下是ABA问题的产生===");
        new Thread(()->{
            atomicReference.compareAndSet(100,101);
            atomicReference.compareAndSet(101,100);
        },"t1").start();

        new Thread(()->{
            //先暂停1秒 保证完成ABA
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(atomicReference.compareAndSet(100, 2019)+"\t"+atomicReference.get());
        },"t2").start();
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println("===以下是ABA问题的解决===");

        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
            //暂停1秒钟t3线程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }

            stampedReference.compareAndSet(100,101,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第2次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
            stampedReference.compareAndSet(101,100,stampedReference.getStamp(),stampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"\t 第3次版本号"+stampedReference.getStamp()+"\t值是"+stampedReference.getReference());
        },"t3").start();

        new Thread(()->{
            int stamp = stampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"\t 第1次版本号"+stamp+"\t值是"+stampedReference.getReference());
            //保证线程3完成1次ABA
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            boolean result = stampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName()+"\t 修改成功否"+result+"\t最新版本号"+stampedReference.getStamp());
            System.out.println("最新的值\t"+stampedReference.getReference());
        },"t4").start();
    }

3.2、循环时间长开销大

问题描述:自旋CAS如果长时间不成功,会给CPU带来非常大的执行开销。

解决办法:JVM支持处理器提供的pause指令,使得效率会有一定的提升,pause指令有两个作用

  • 第一它可以延迟流水线执行指令,使CPU不会消耗过多的执行资源,
  • 第二它可以避免在退出循环的时候因内存顺序冲突(memory order violation)而引起CPU流水线被清空(CPU pipeline flush),从而提高CPU的执行效率。

3.3、不能保证多个共享变量的原子操作

问题描述:当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可以用来保证原子性。 解决办法:从Java1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,你可以把多个变量放在一个对象里来进行CAS操作

四、concurrent包的实现

由于java的CAS同时具有 volatile 读和volatile写的内存语义,因此Java线程之间的通信现在有了下面四种方式:

  1. A线程写volatile变量,随后B线程读这个volatile变量。
  2. A线程写volatile变量,随后B线程用CAS更新这个volatile变量。
  3. A线程用CAS更新一个volatile变量,随后B线程读这个volatile变量。
  4. A线程用CAS更新一个volatile变量,随后B线程用CAS更新这个volatile变量。

Java的CAS会使用现代处理器上提供的高效机器级别原子指令,这些原子指令以原子方式对内存执行读-改-写操作,这是在多处理器中实现同步的关键(从本质上来说,能够支持原子性读-改-写指令的计算机器,是顺序计算图灵机的异步等价机器,因此任何现代的多处理器都会去支持某种能对内存执行原子性读-改-写操作的原子指令)。同时,volatile变量的读/写和CAS可以实现线程之间的通信。把这些特性整合在一起,就形成了整个concurrent包得以实现的基石。如果我们仔细分析concurrent包的源代码实现,会发现一个通用化的实现模式:

  • 首先,声明共享变量为volatile;
  • 然后,使用CAS的原子条件更新来实现线程之间的同步;
  • 同时,配合以volatile的读/写和CAS所具有的volatile读和写的内存语义来实现线程之间的通信。

AQS非阻塞数据结构原子变量类(java.util.concurrent.atomic包中的类),这些concurrent包中的基础类都是使用这种模式来实现的,而concurrent包中的高层类又是依赖于这些基础类来实现的。从整体来看,concurrent包的实现示意图如下:

注:AQS(AbstractQueuedSynchronizer抽象队列同步器),提供了一种实现阻塞锁和一系列依赖FIFO等待队列的同步器的框架。

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

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2022年4月1,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
java架构之路(多线程)原子操作,Atomic与Unsafe魔术类
  这次不讲原理了,主要是一些应用方面的知识,和上几次的JUC并发编程的知识点更容易理解.
小菜的不能再菜
2020/02/21
4760
JAVA并发编程系列(3)JUC包之CAS原理
首先,Atomic包,原子操作类,提供了用法简单、性能高效、最重要是线程安全的更新一个变量。支持整型、长整型、布尔、double、数组、以及对象的属性原子修改,支持种类非常丰富。
拉丁解牛说技术
2024/09/06
1380
Java并发编程CAS
它的功能是判断内存某一个位置的值是否为预期,如果是则更改这个值,这个过程就是原子的。
一觉睡到小时候
2020/05/27
4730
java吧_死磕好不好
前面我们说到volatile不保证原子性,解决办法就是使用AtomicInteger代替int,但是为什么使用AtomicInteger就可以保证了原子性了,是因为AtomicInteger实现的就是CAS思想和Unsafe的支持。
全栈程序员站长
2022/08/04
7210
CAS——比加锁更高效的多线程并发场景下数据一致性解决方案
cheese
2024/06/15
1240
CAS——比加锁更高效的多线程并发场景下数据一致性解决方案
CAS Krains 2020-08-25
其中的关键是 compareAndSet,它的简称就是 CAS (也有 Compare And Swap 的说法),该方法是原子的。
Krains
2020/09/10
3330
CAS  Krains 2020-08-25
多线程编程学习八(原子操作类).
Java 在 JDK 1.5 中提供了 java.util.concurrent.atomic 包,这个包中的原子操作类提供了一种用法简单、性能高效、线程安全地更新一个变量的方式。主要提供了四种类型的原子更新方式,分别是原子更新基本类型、原子更新数组、原子更新引用和原子更新属性。
JMCui
2019/09/09
3070
多线程编程学习八(原子操作类).
CAS之比较并交换
compare and swap的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。它包含三个操作数——内存位置、预期原值及更新值。 执行CAS操作的时候,将内存位置的值与预期原值比较: 如果相匹配,那么处理器会自动将该位置值更新为新值, 如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
鱼找水需要时间
2023/02/16
4080
CAS之比较并交换
【死磕Java并发】—- 深入分析CAS
CAS,Compare And Swap,即比较并交换。Doug lea大神在同步组件中大量使用CAS技术鬼斧神工地实现了Java多线程的并发操作。整个AQS同步组件、Atomic原子类操作等等都是以
芋道源码
2018/03/01
7810
【死磕Java并发】—- 深入分析CAS
【Java】CAS及其缺点和解决方案梳理
CAS 英文就是 compare and swap ,也就是比较并交换,首先它是一个原子操作,可以避免被其他线程打断。在Java并发中,最初接触的应该就是Synchronized关键字了,但是Synchronized属于重量级锁,很多时候会引起性能问题,虽然在新的 JDK 中对其已经进行了优化。volatile也是个不错的选择,但是volatile不能保证原子性,只能在某些场合下使用。那么问题来了,这个 CAS 机制是怎么在不加锁的情况下来保证共享资源的互斥呢?
后端码匠
2023/09/02
4420
【Java】CAS及其缺点和解决方案梳理
简单理解CAS
CAS(Compare And Set)比较交换,是一种无锁算法。即不使用锁的方式来实现多线程同步。由于是无锁的策略,也就是在没有线程被阻塞的情况下实现变量同步,所以也叫非阻塞同步(Non-blocking Synchronization)。
有一只柴犬
2024/01/25
1420
简单理解CAS
Java并发编程之cas理论(无锁并发)
前面看到的AtomicInteger的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
冬天vs不冷
2025/01/21
800
Java并发编程之cas理论(无锁并发)
JUC 多线程 CAS 算法
解释:一个线程在使用atomicInteger原子变量进行修改值的操作中,底层的CAS算法会拿自己工作空间的值去和主内存空间的值去比较,如果主内存值和期望数值5相同,则去修改为2019,否则修改失败。即CAS有三个操作数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,将内存值修改为B并返回true,否则什么都不做并返回false。
万能青年
2019/08/30
4200
CAS 原理深入剖析,深入内核源码的那种
则先读取 A 的当前值 E 为 2,在内存计算结果 V 为 3,比较之前读出来的 A 的当前值 2 和 最新值,如果最新值为 2 ,表示这个值没有被别人改过,则放心的把最终的值更新为 3.
kk大数据
2021/03/13
1K0
深入理解JUC:第三章:AtomicReference原子引用
第一章讲解了volatile不保证原子性,为解决原子性使用了AtomicInteger原子整型,解决了基本类型运算操作的原子性的问题,那我们自定义的实体类或者基本数据类型都要保证原子性呢?使用AtomicReference原子引用
Java廖志伟
2022/09/28
3650
深入理解JUC:第三章:AtomicReference原子引用
一文打通CAS
CAS(compare and swap)的缩写,中文翻译成比较并交换,实现并发算法时常用到的一种技术。 它包含三个操作数——内存位置、预期原值及更新值。 执行CAS操作的时候,将内存位置的值与预期原值比较:如果相匹配,那么处理器会自动将该位置值更新为新值,如果不匹配,处理器不做任何操作,多个线程同时执行CAS操作只有一个会成功。
一个风轻云淡
2023/10/15
2570
一文打通CAS
JUC系列(九) CAS 与锁的理解
CAS:比较当前工作内存中的值,如果这个值是期望的,那么执行操作,如果不是就一直循环
冷环渊
2022/12/03
2890
JUC系列(九) CAS 与锁的理解
全面了解 Java 原子变量类
保证线程安全是 Java 并发编程必须要解决的重要问题。Java 从原子性、可见性、有序性这三大特性入手,确保多线程的数据一致性。
静默虚空
2020/01/02
8510
高并发Java(4):无锁
在高并发Java(1):前言中已经提到了无锁的概念,由于在jdk源码中有大量的无锁应用,所以在这里介绍下无锁。
用户5640963
2019/07/26
5320
JUC学习之无锁---乐观锁(非阻塞)
前面看到的 AtomicInteger 的解决方法,内部并没有用锁来保护共享变量的线程安全。那么它是如何实现的呢?
大忽悠爱学习
2022/01/10
7370
JUC学习之无锁---乐观锁(非阻塞)
相关推荐
java架构之路(多线程)原子操作,Atomic与Unsafe魔术类
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验