CAS详解

CAS概述

CAS 是 Java 中 Unsafe 类里面的方法,它的全称是 CompareAndSwap,比较并交换的意思。它的主要功能是能够保证在多线程环境下,对于共享变量的修改的原子性。
cas 是一种基于锁的操作,而且是乐观锁。在 java 中锁分为乐观锁和悲观锁。悲观锁是将资源锁住,等一个之前获得锁的线程释放锁之后,下一个线程才可以访问。而乐观锁采取了一种宽泛的态度,通过某种方式不加锁来处理资源,比如通过给记录加 version 来获取数据,性能较悲观锁有很大的提高。
CAS 是一种强大的原子操作机制,用于实现无锁并发编程。通过硬件支持的原子指令,CAS 能够在多线程环境中高效地进行原子操作,避免了锁的开销。然而,CAS 操作也有其局限性,如 ABA 问题和自旋消耗等。理解和正确使用 CAS 对于编写高效的并发程序至关重要。
CAS(Compare and Swap,比较并交换)是一种并发编程中常用的原子操作,通常用于实现无锁算法。CAS 操作包括三个操作数:内存位置(V)、预期原值(A)和新值(B)。CAS 的操作步骤如下:

  1. 读取内存位置 V 的当前值,记为 A。
  2. 判断当前值 A 是否等于预期原值 A,如果相等,则执行第4步;如果不相等,则执行第3步。
  3. 不相等情况下,说明该内存位置的值已经被其他线程修改过了,本次 CAS 操作失败,需要重新从步骤1开始尝试。
  4. 将内存位置 V 的值设置为新值 B,如果成功将值设置为 B,则表示 CAS 操作成功。
    CAS 操作是原子性的,它能够确保在多线程环境下对共享变量的原子操作。CAS 操作通常用于实现乐观锁机制,在并发编程中可以避免使用传统的锁机制,减少了线程上下文的切换,提高了并发性能。
    在 Java 中,Java.util.concurrent 包提供了 Atomic 包下的原子类(如 AtomicInteger、AtomicLong 等),这些类都是基于 CAS 操作实现的。通过使用这些原子类,开发者可以在不使用显式锁的情况下实现线程安全的操作。
    需要注意的是,CAS 操作也存在一些问题,比如 ABA 问题(即某个值从 A 变成 B 又变回 A,在这期间另一个线程可能会误以为这个值没有发生变化而进行操作),以及自旋重试导致的性能问题。因此,在使用 CAS 时需要考虑这些问题,并结合具体情况谨慎选择合适的并发控制方式。
    CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存地址里面的值和 A 的值是一样的,那么就将内存里面的值更新成 B。CAS 是通过无限循环来获取数据的,若果在第一轮循环中,a 线程获取地址里面的值被 b 线程修改了,那么 a 线程需要自旋,到下次循环才有可能机会执行
    java.util.concurrent.atomic 包 下 的 类 大 多 是 使 用 CAS操作来 实 现(AtomicInteger,AtomicBoolean,AtomicLong)
    CAS 主要用在并发场景中,比较典型的使用场景有两个
    第一个是 J.U.C 里面 Atomic 的原子实现,比如 AtomicInteger,AtomicLong
    第 二 个 是 实 现 多 线 程 对 共 享 资 源 竞 争 的 互 斥 性 质 , 比 如 在 AQS 、ConcurrentHashMap、ConcurrentLinkedQueue 等都有用到

基本原理

CAS 是一种原子操作,涉及三个操作数:
● 内存位置 V:需要操作的变量。
● 预期值 A:期望该变量当前的值。
● 新值 B:想要将该变量更新为的新值。
CAS 操作的工作方式如下:

  1. 比较内存位置 V 的当前值是否等于预期值 A。
  2. 如果相等,则将内存位置 V 的值更新为新值 B。
  3. 如果不相等,则不做任何操作,并返回当前值。
    通过这种方式,CAS 能够在没有锁的情况下确保操作的原子性。
    没有获取到锁的线程是不会阻塞的,通过循环控制一直不断的获取锁。
    CAS: Compare and Swap,翻译成比较并交换。 执行函数 CAS(V,E,N) CAS 有 3 个操作数,内存值 V,旧的预期值 E,要修改的新值 N。当且仅当预期值 E 和内 存值 V 相同时,将内存值 V 修改为 N,否则什么都不做。

Cas 是通过硬件指令,保证原子性
2Java 是通过 unsafe jni 技术
原子类: AtomicBoolean,AtomicInteger,AtomicLong 等使用 CAS 实现。
优点:没有获取到锁的线程,会一直在用户态,不会阻塞,没有锁的线程会一直通过循环控
制重试。
缺点:通过死循环控制,消耗 cpu 资源比较高,需要控制循次数,避免 cpu 飙高问题;
Cas 本质的原理:
旧的预期值===v(共享变量中值),才会修改我们 v。
基于 cas 实现锁机制原理
Cas 无锁机制原理:

  1. 定义一个锁的状态;
  2. 状态状态值=0 则表示没有线程获取到该锁;
  3. 状态状态值=1 则表示有线程已经持有该锁;
    实现细节:
    CAS 获取锁:
    将该锁的状态从 0 改为 1-----能够修改成功 cas 成功则表示获取锁成功
    如果获取锁失败–修改失败,则不会阻塞而是通过循环(自旋来控制重试)
    CAS 释放锁:
    将该锁的状态从 1 改为 0 如果能够改成功 cas 成功则表示释放锁成功。

使用场景

原子变量类:
Java 提供了一些基于 CAS 的原子变量类,如 AtomicInteger、AtomicLong、AtomicReference 等,用于实现无锁的并发编程。
高效的并发数据结构:
许多高效的并发数据结构,如无锁队列、无锁栈等,都是基于 CAS 实现的。
乐观锁机制:
CAS 可以用来实现乐观锁机制,适用于频繁读写但冲突较少的场景。
实现细节
在 Java 中,CAS 操作通常通过 Unsafe 类的 compareAndSwap 方法实现。Unsafe 类直接访问硬件提供的原子操作指令,如 cmpxchg(x86 指令集)。
以下是一个简单的 AtomicInteger 实现示例:

import sun.misc.Unsafe;
import java.lang.reflect.Field;

public class SimpleAtomicInteger {
    private static final Unsafe unsafe;
    private static final long valueOffset;
    private volatile int value;

    static {
        try {
            Field field = Unsafe.class.getDeclaredField("theUnsafe");
            field.setAccessible(true);
            unsafe = (Unsafe) field.get(null);
            valueOffset = unsafe.objectFieldOffset(SimpleAtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) {
            throw new Error(ex);
        }
    }

    public SimpleAtomicInteger(int initialValue) {
        value = initialValue;
    }

    public int get() {
        return value;
    }

    public void set(int newValue) {
        value = newValue;
    }

    public boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

    public int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next)) {
                return next;
            }
        }
    }
}

优缺点

优点
高效:
CAS 操作避免了使用锁的开销,能够在多线程环境中实现高效的原子操作。
无锁并发:
通过 CAS 可以实现无锁并发数据结构,减少线程阻塞,提高并发性能。
缺点
ABA 问题:
在 CAS 操作中,可能会出现 ABA 问题,即一个变量从值 A 变为值 B,然后又变回值 A,此时 CAS 操作无法检测到变化。可以使用版本号或 AtomicStampedReference 来解决 ABA 问题。
自旋消耗:
如果多线程竞争激烈,CAS 操作可能会导致线程自旋重试,消耗大量 CPU 资源。
复杂性:
编写和调试基于 CAS 的算法和数据结构较为复杂,容易引入难以发现的并发问题。

CAS 的问题

CAS 的优点在于它是一种非阻塞算法,即在不使用锁(lock)的情况下实现多线程之间的变量同步,从而大大减小了线程间竞争,提高了吞吐量。但是,CAS 也存在一些问题:

  1. ABA 问题:如果一个变量 V 初次读取的时候是 A 值,并且在准备写入的时候检查到它仍然为 A 值,那么我们就认为它没有被其他线程修改过,然后新值 B 就会被写入到 V 中。但是,如果在这两次检查之间,变量 V 的值被其他线程先改成了 B,然后又改回 A,那么当前线程写入的值就覆盖了其他线程的工作。这就是所谓的 ABA 问题。Java 中的 AtomicStampedReference 可以通过引入版本号戳来解决这个问题。
  2. 循环开销:由于 CAS 操作可能会失败(例如,因为其他线程已经修改了值),所以它通常在一个循环中进行重试,直到成功为止。这可能会引入额外的开销。
  3. 只能保证单个共享变量的原子操作:对于涉及多个共享变量的操作,CAS 无法保证这些变量的原子性。这种情况下,可能需要使用锁或其他同步机制。
    (1)CAS 容易造成 ABA 问题一个线程 a 将数值改成了 b,接着又改成了 a,此时 CAS 认为是没有变化,其实是已经变化过了,而这个问题的解决方案可以使用版本号标识,每操作一次 version 加 1。在 java5中,已经提供了 AtomicStampedReference 来解决问题
    (2)不能保证代码块的原子性CAS 机制所保证的知识一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证 3 个变量共同进行原子性的更新,就不得不使用 synchronized 了
    (3)CAS 造成 CPU 利用率增加之前说过了 CAS 里面是一个循环判断的过程,如果线程一直没有获取到状态,cpu 资源会直被占用。

解决 ABA 问题
ABA 问题是 CAS 操作中的一个常见问题。可以通过版本号来解决这个问题,即每次更新时同时更新一个版本号,以确保操作的原子性。

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABAExample {
    private static final AtomicStampedReference<Integer> atomicStampedRef = new AtomicStampedReference<>(100, 0);

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
            atomicStampedRef.compareAndSet(101, 100, stamp + 1, stamp + 2);
        });

        Thread t2 = new Thread(() -> {
            int stamp = atomicStampedRef.getStamp();
            try {
                Thread.sleep(1000);  // 确保 t1 先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean success = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
            System.out.println("t2 CAS 操作结果:" + success);  // false,因为 t1 已经更改了版本号
        });

        t1.start();
        t2.start();
    }
}

在这个示例中,使用 AtomicStampedReference 解决了 ABA 问题,确保 CAS 操作的正确性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

思静语

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值