• 并发编程之深入理解CAS


    并发编程之深入理解CAS

    什么是 CAS

    CAS,compare and swap的缩写,中文翻译成比较并交换。

    CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。

    为什么要使用 CAS

    在并发中,我们需要对一个数据进行更改,如果使用锁来保证原子性,首先在性能方面会设计到底层操作系统内核线程切换,这个开销是很大的,如果使用 CAS 实现的话,相比较之下不会设计到内核切换,开销比较轻。

    使用伪代码表示

    if (value == expectedValue) {
      value = newValue;
     }
    
    • 1
    • 2
    • 3

    一个由比较和赋值两阶段组成的复合操作,CAS 可以看作是它们合并后的整体 ——一个不可分割的原子操作,并且其原子性是直接在硬件层面得到保障的。

    CAS可以看做是乐观锁(对比数据库的悲观、乐观锁)的一种实现方式,Java原子类中的递增操 作就通过CAS自旋实现的。 CAS是一种无锁算法,在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步。

    • 1 首先读取内存中的实际值v 如果和自己的值E相等,计算赋值给更新的值 U 成功则返回 U
    • 2 上面更新失败,则继续重试,成功则返回,失败则继续重试。

    CAS应用

    在Java中使用 CAS

    CAS 操作是由 Unsafe 类提供支持的,该类定义了三种针对不同类型变量的 CAS 操 作

    可以看到是本地方法,是由 JVM 实现的。 接收四个参数分别是

    对象的实例,内存偏移量,字段期望值,字段新值。

    CAS缺陷

    CAS 虽然解决了原子性,但是有一些问题

    • ABA 问题
    • 只能保证一个共享变量的原子操作
    • CAS 不成功,长时间会造成 CPU 空转,影响 CPU 开销

    ABA问题

    当有多个线程对一个原子类进行操作的时候,某个线程在短时间内将原子类的值A修改为B,又马 上将其修改为A,此时其他线程不感知,还是会修改成功。

    main 线程不清楚另一个线程对这个变量进行了修改,可能误认为没有更改过。

    ABA问题的解决

    可以在每一次修改时给一个版本号,这也就是乐观锁的由来。

    比如 Java中AtomicStampedReference类就提供了这样的功能

    三个线程 t1,t2,t3 分别对变量进行修改,最后t3在更改为初始值。

      AtomicStampedReference atomicStampedReference = new AtomicStampedReference<>(1, 1);
    
            new Thread(() -> {
                boolean b = atomicStampedReference.compareAndSet(1, 2, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                while (b) {
                    if (b) {
                        System.out.println(" t1 修改成功  ");
                        return;
                    } else {
                        System.out.println(" t1 修改失败  ");
                    }
                }
            }, "t1 ").start();
    
            new Thread(() -> {
                boolean b = atomicStampedReference.compareAndSet(2, 3, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                while (b) {
                    if (b) {
                        System.out.println(" t2 修改成功  ");
                        return;
                    } else {
                        System.out.println(" t2 修改失败  ");
                    }
                }
    
            }, "t2 ").start();
    
            new Thread(() -> {
                boolean b = atomicStampedReference.compareAndSet(3, 1, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
                while (b) {
                    if (b) {
                        System.out.println(" t3 修改成功  ");
                        return;
    
                    } else {
                        System.out.println(" t3 修改失败  ");
                    }
                }
    
            }, "t
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
  • 相关阅读:
    基于tensorflow的cnn文本分类
    音视频编码格式-AAC ADT
    【Spring Boot】单元测试
    ansible常见避坑指南
    Automatic differentiation package - torch.autograd
    性能测试基础
    【CTFHUB】SSRF原理之简单运用(一)
    FusionCharts Suite XT
    第三章:form表单
    鸿蒙开发:【进程模型概述】
  • 原文地址:https://blog.csdn.net/Huangjiazhen711/article/details/127666027