【003】Java中的CAS操作是什么?它如何实现无锁编程?


CAS(Compare-And-Swap)操作在Java中的含义及无锁编程实现:
1、原子操作: CAS是一种基于比较和交换的原子操作,用于实现无锁编程。

2、实现方式: 通过循环比较当前值和预期值,如果相同则更新为新值。

3、无锁优势: 减少线程阻塞,提高系统吞吐量。

4、ABA问题: CAS可能面临ABA问题,可以通过版本号等机制解决。

一、CAS的定义

1.1 概述

CAS 是 Compare and Swap 的缩写,中文意思是比较并交换。

它是一种原子操作,底层通常由 CPU 指令直接支持。其核心思想可以用一个简单的函数来表示:

/**
 * @param V 要更新的变量在内存中的值
 * @param E 预期的值(我们认为变量现在应该是什么)
 * @param N 希望将变量更新为的新值
 * @return 如果成功更新,返回 true;否则返回 false
 */
boolean compareAndSwap(V, E, N);

CAS 操作的原子性保证了整个过程是不可分割的。它会执行以下三个步骤:

  • 读取:从内存地址 V 读取当前值。
  • 比较:将读取到的值与预期值 E 进行比较。
  • 交换:如果比较结果相等(即 V 的值确实是 E),则将 V 的值更新为新值 N。如果不相等,则什么也不做。
    整个过程是 “原子” 的,意味着在执行期间,其他任何线程都无法修改 V 的值。

1.2 Java中的CAS定义

Java 本身并没有直接暴露 CAS 操作给开发者,而是通过 java.util.concurrent.atomic 包中的一系列原子类来提供 CAS 功能。这些类的内部都依赖于一个非常特殊的类 sun.misc.Unsafe。

✅ sun.misc.Unsafe

Unsafe 类提供了一系列低级别、不安全的操作,其中就包括直接对内存进行 CAS 的方法:

compareAndSwapObject(Object o, long offset, Object expected, Object update)
compareAndSwapInt(Object o, long offset, int expected, int update)
compareAndSwapLong(Object o, long offset, long expected, long update)

这些方法是 native 的,意味着它们的实现不是用 Java 写的,而是调用了底层的 C++ 代码,最终会执行 CPU 的 CAS 指令(例如 x86 架构下的 cmpxchg 指令)。

1.2 原子类的示例: AtomicInteger

我们以 AtomicInteger 为例,看看 CAS 是如何被应用的。

import java.util.concurrent.atomic.AtomicInteger;

public class AtomicIntegerDemo {
    public static void main(String[] args) {
        AtomicInteger count = new AtomicInteger(0);

        // 自增 1,并返回新值
        int newValue = count.incrementAndGet(); 
        System.out.println(newValue); // 输出: 1

        // 比较并设置:如果当前值是 1,则更新为 100
        boolean success = count.compareAndSet(1, 100);
        System.out.println(success); // 输出: true
        System.out.println(count.get()); // 输出: 100

        // 再次尝试比较并设置:当前值已不是 1,所以失败
        success = count.compareAndSet(1, 200);
        System.out.println(success); // 输出: false
        System.out.println(count.get()); // 输出: 100 (值未改变)
    }
}

incrementAndGet() 方法的内部实现大致如下(简化版):

public final int incrementAndGet() {
    for (;;) { // 无限循环,直到成功
        int current = get(); // 获取当前值
        int next = current + 1; // 计算目标值
        // 执行 CAS 操作
        if (compareAndSet(current, next)) {
            return next; // 成功则返回新值
        }
        // 如果失败(说明 current 被其他线程修改了),则循环重试
    }
}

这个 “循环 + CAS”的模式是无锁编程中最核心、最经典的模式,通常被称为“自旋(Spin)”

1.3 CAS 如何实现无锁编程

传统的并发控制依赖于锁(如 synchronized 或 ReentrantLock),这是一种 悲观锁(Pessimistic Locking)策略。它假设冲突一定会发生,因此在访问共享资源前,必须先获取锁,阻止其他线程访问。这种方式会带来线程阻塞、上下文切换等开销,在高并发下性能不佳。

而 CAS 基于 乐观锁(Optimistic Locking)策略。它假设冲突很少发生,因此不预先加锁,而是直接尝试去更新共享资源。如果发现冲突(CAS 失败),也不阻塞,而是立即重试,直到成功为止。

⬆️ 无锁编程的实现步骤通常如下:

  1. 定义共享变量:使用 volatile 修饰共享变量,确保其内存可见性。当一个线程修改了变量,其他线程能立即看到最新的值。
  2. 循环 CAS:在修改共享变量的方法中,使用一个无限循环。在循环内部:
    1. 获取变量的当前值。
    1. 根据当前值计算出想要更新的新值。
    1. 调用 CAS 操作尝试更新。
    1. 如果 CAS 成功,跳出循环,方法结束。
    1. 如果 CAS 失败,说明在步骤 a 和 c 之间,变量被其他线程修改了。此时无需做任何处理,直接回到步骤 a,再次尝试整个过程。

🌻 示例:自定义无锁计数器

  • 我们可以模仿 AtomicInteger,用 Unsafe 来手动实现一个简单的无锁计数器,以更深入地理解这个过程。

  • 注意:Unsafe 类是 JDK 内部使用的,不建议开发者在生产代码中直接使用,因为它非常底层且不安全。

二、CAS优缺点

2.1 优点

  • 高性能:在低冲突或无冲突的场景下,CAS 避免了线程阻塞和上下文切换的开销,性能远高于锁。
  • 无死锁:由于没有锁的概念,也就不存在死锁的问题。
  • 灵活性:CAS 是一种更细粒度的操作,使得构建各种高性能的并发数据结构成为可能。

2.2 缺点

  1. ABA 问题:这是 CAS 最著名的问题。

    • 场景:线程 A 读取变量值为 A。线程 B 将变量修改为 B,然后又修改回 A。线程 A 执行 CAS 时,发现变量值仍然是 A,于是成功更新。但此时变量已经被修改过了,可能导致逻辑错误。
    • 解决方案:可以使用版本号或时间戳来解决。Java 中提供了 AtomicStampedReference 和 AtomicMarkableReference 两个类来应对 ABA 问题。它们在进行 CAS 操作时,不仅比较值,还会比较一个 “戳”(stamp)或 “标记”(mark)。
  2. 自旋消耗 CPU:在高冲突的场景下,CAS 会频繁失败并进入循环重试,这会持续消耗 CPU 资源,导致 “自旋锁” 的性能急剧下降。

  3. 只能保证单个变量的原子操作:CAS 只能对单个变量进行原子更新。如果需要对多个变量进行复合原子操作(例如,先检查变量 A,再根据 A 的值更新变量 B),CAS 就无法直接满足,需要更复杂的算法(如事务内存)或回到锁的方式。

三、总结

  • CAS 是一种底层的、原子的比较并交换操作,是实现无锁编程的基础。
  • 它通过乐观锁策略,采用 “循环 + CAS”(自旋)的模式来更新共享资源,避免了锁的开销。
  • Java 中的 java.util.concurrent.atomic 包提供了一系列基于 CAS 的原子类,方便开发者进行无锁编程。
  • CAS 并非完美无缺,它存在 ABA 问题、高冲突下 CPU 自旋消耗等缺点,在实际应用中需要根据场景进行选择。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值