Java CAS原理和用法总结

 更新时间:2026年01月12日 09:24:30   作者:看透也说透kevin  
CAS(Compare And Swap)是一种无锁的原子操作机制,通过一条CPU硬件指令(如x86架构的CMPXCHG指令)实现,能保证操作的原子性,本文给大家介绍java CAS原理和用法,感兴趣的朋友跟随小编一起看看吧

一、CAS 原理

1. 核心思想

CAS 是一种无锁的原子操作机制。它的核心思想是:我认为值应该是A,如果是,那我就把它改成B;如果不是A(说明被别人改过了),那我就不修改,然后可以选择重试或放弃。

这个操作是作为一条CPU硬件指令实现的(在x86架构上是 CMPXCHG 指令),因此它能保证原子性,不会被线程调度打断。

2. 操作模型

CAS 操作涉及三个操作数:

  • 内存位置(V)
  • 预期的原值(A)
  • 新值(B)

伪代码逻辑如下:

if (V == A) {
V = B;
return true;
} else {
return false;
}

但关键是,整个比较和交换的过程是一个不可分割的原子操作

3. 工作流程

当一个线程想要更新一个变量时,它会:

  1. 获取当前内存中的值,作为期望值 A
  2. 计算出新值 B
  3. 执行 CAS 指令,判断当前内存中的值是否还是 A
    • 如果是,说明没有其他线程修改过,成功将值更新为 B
    • 如果不是,说明值已被其他线程修改,本次更新失败。线程通常会重试整个操作(获取新的当前值,计算新值,再次执行CAS),直到成功为止。这种重试行为就是常见的自旋

4. 优点与缺点

  • 优点
    • 高性能:避免了重量级锁(如 synchronized)带来的线程阻塞、唤醒和上下文切换的开销,在竞争不激烈的场景下性能极高。
    • 避免死锁:由于是无锁操作,从根本上避免了死锁问题。
  • 缺点
    • ABA 问题:CAS 只检查值是否变化,但如果一个值从 A 变成 B,又被改回 A,CAS 会误以为它没变。解决方案是使用版本号标记(如 AtomicStampedReference)。
    • 自旋开销:在高竞争环境下,如果线程一直失败重试,会长时间占用 CPU,消耗资源。
    • 只能保证一个共享变量的原子操作:对于多个共享变量,CAS 无法保证原子性。但可以将它们合并成一个对象,使用 AtomicReference 来保证原子性。

二、Java 中的 CAS 用法

在 Java 中,你不能直接使用 CPU 指令。CAS 的能力是通过 sun.misc.Unsafe 类中的本地(Native)方法提供的。但通常,我们不会直接使用 Unsafe,而是使用 JD 在 java.util.concurrent.atomic 包下为我们封装好的原子类

1. 主要的原子类

  • 基本类型
    • AtomicInteger:整型原子类
    • AtomicLong:长整型原子类
    • AtomicBoolean:布尔型原子类
  • 数组类型
    • AtomicIntegerArray:整型数组原子类
    • AtomicLongArray:长整型数组原子类
    • AtomicReferenceArray:引用类型数组原子类
  • 引用类型
    • AtomicReference:引用类型原子类
    • AtomicMarkableReference:带标记位的引用类型原子类(解决ABA问题的一种方式)
    • AtomicStampedReference带版本号的引用类型原子类(解决ABA问题的标准方案)
  • 字段更新器
    • AtomicIntegerFieldUpdater:基于反射,原子性地更新某个类的 volatile int 字段。
    • AtomicLongFieldUpdater
    • AtomicReferenceFieldUpdater

2. 核心方法

所有原子类都提供了基于 CAS 的核心方法:

  • boolean compareAndSet(int expect, int update)
    • 这是最核心的方法!如果当前值等于期望值 expect,则原子地将值设置为 update,成功返回 true,失败返回 false
  • int getAndSet(int newValue)
    • 原子地设置为新值,并返回旧值。底层通常通过循环 CAS 实现。
  • int getAndIncrement() / int getAndDecrement()
    • 原子地递增/递减 1,返回旧值。i++ 的原子版本。
  • int getAndAdd(int delta)
    • 原子地加上 delta,返回旧值。
  • int incrementAndGet() / int decrementAndGet()
    • 原子地递增/递减 1,返回新值。++i 的原子版本。

3. 代码示例

示例 1:使用 AtomicInteger 实现线程安全的计数器

import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
    public static void main(String[] args) throws InterruptedException {
        AtomicInteger atomicInt = new AtomicInteger(0);
        Runnable task = () -> {
            for (int i = 0; i < 1000; i++) {
                // 内部通过循环CAS操作实现原子递增
                atomicInt.incrementAndGet();
            }
        };
        Thread thread1 = new Thread(task);
        Thread thread2 = new Thread(task);
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
        // 结果总是 2000,保证了原子性
        System.out.println("Final Count: " + atomicInt.get()); 
    }
}

示例 2:手动使用 compareAndSet 进行自旋

AtomicInteger atomicInt = new AtomicInteger(0);
int oldValue, newValue;
do {
    oldValue = atomicInt.get(); // 获取当前值作为预期值
    newValue = oldValue + 1;    // 计算新值
} while (!atomicInt.compareAndSet(oldValue, newValue)); 
// 如果CAS失败(oldValue已不是当前值),则循环重试

示例 3:使用 AtomicStampedReference 解决 ABA 问题

import java.util.concurrent.atomic.AtomicStampedReference;
public class ABADemo {
    // 初始值为 100,版本号(Stamp)为 0
    static AtomicStampedReference<Integer> atomicStampedRef = 
            new AtomicStampedReference<>(100, 0);
    public static void main(String[] args) throws InterruptedException {
        int initialStamp = atomicStampedRef.getStamp(); // 获取初始版本号
        // 线程1模拟ABA操作
        Thread thread1 = new Thread(() -> {
            // 先改成 101,版本号+1
            atomicStampedRef.compareAndSet(100, 101, initialStamp, initialStamp + 1);
            // 再改回 100,版本号再+1
            atomicStampedRef.compareAndSet(101, 100, initialStamp + 1, initialStamp + 2);
        });
        // 线程2尝试修改
        Thread thread2 = new Thread(() -> {
            // 先睡一会儿,确保线程1完成了ABA操作
            try { Thread.sleep(1000); } catch (InterruptedException e) {}
            // 尝试修改。虽然期望值还是100,但版本号已经从0变成了2,所以CAS会失败!
            boolean success = atomicStampedRef.compareAndSet(
                    100, 
                    202, 
                    initialStamp, // 传入旧的版本号0
                    initialStamp + 1
            );
            System.out.println("CAS successful? " + success); // 输出:false
        });
        thread1.start();
        thread2.start();
        thread1.join();
        thread2.join();
    }
}

总结

特性描述
本质一条CPU原子指令,通过 Unsafe 类提供给 Java 开发者使用。
实现JDK 的 java.util.concurrent.atomic 包下的原子类对其进行了封装。
核心方法compareAndSet(expectedValue, newValue)
优点无锁高性能(低竞争时)、避免死锁
缺点ABA问题(用版本号解决)、自旋CPU开销(高竞争时)。
应用场景计数器、序列号生成器、ConcurrentHashMap 等高性能并发容器的实现。

CAS 是现代并发包(JUC)的基石,理解了它就能更好地理解 ReentrantLock、线程池等高级并发工具的内部工作原理。

到此这篇关于java CAS原理和用法的文章就介绍到这了,更多相关java CAS原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Java创建对象之显示创建与隐式创建

    Java创建对象之显示创建与隐式创建

    在本篇文章中,小编会带大家学习面向对象中关于对象的创建之显示创建和隐式创建,其实类和对象作为面向对象中最基本的,也是最重要的,需要的朋友可以参考下
    2023-05-05
  • Docker和 Containerd 的区别解析

    Docker和 Containerd 的区别解析

    containerd 是一个来自 Docker 的高级容器运行时,并实现了 CRI 规范,它是从 Docker 项目中分离出来,之后 containerd 被捐赠给云原生计算基金会(CNCF)为容器社区提供创建新容器解决方案的基础,这篇文章主要介绍了Docker和 Containerd 的区别,需要的朋友可以参考下
    2024-03-03
  • Java实现Kafka生产者消费者代码实例

    Java实现Kafka生产者消费者代码实例

    这篇文章主要介绍了Java实现Kafka生产者消费者代码实例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-04-04
  • SpringBoot静态资源目录访问

    SpringBoot静态资源目录访问

    今天小编就为大家分享一篇关于SpringBoot静态资源目录访问,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧
    2019-01-01
  • WebService教程详解(一)

    WebService教程详解(一)

    WebService,顾名思义就是基于Web的服务。它使用Web(HTTP)方式,接收和响应外部系统的某种请求,接下来通过本文给大家介绍WebService教程详解(一),对webservice教程感兴趣的朋友一起学习吧
    2016-03-03
  • SpringBoot拦截器实现项目防止接口重复提交

    SpringBoot拦截器实现项目防止接口重复提交

    基于SpringBoot框架来开发业务后台项目时,接口重复提交是一个常见的问题,本文主要介绍了SpringBoot拦截器实现项目防止接口重复提交,具有一定的参考价值,感兴趣的可以了解一下
    2023-09-09
  • MyBatis利用拦截器实现数据脱敏详解

    MyBatis利用拦截器实现数据脱敏详解

    现代网络环境中,敏感数据的处理是至关重要的,敏感数据包括个人身份信息、银行账号、手机号码等,所以本文主要为大家详细介绍了MyBatis如何利用拦截器实现数据脱敏,希望对大家有所帮助
    2023-11-11
  • SpringBoot 注解事务声明式事务的方式

    SpringBoot 注解事务声明式事务的方式

    springboot使用上述注解的几种方式开启事物,可以达到和xml中声明的同样效果,但是却告别了xml,使你的代码远离配置文件。今天就扒一扒springboot中事务使用注解的玩法,感兴趣的朋友一起看看吧
    2017-09-09
  • Java排序算法中的插入排序算法实现

    Java排序算法中的插入排序算法实现

    这篇文章主要介绍了Java排序算法中的插入排序算法实现,插入排序是将数组中的数据分为两个区间,已排序区间和未排序区间,其中已排序区间初始只有一个元素,就是数组的第一个元素,需要的朋友可以参考下
    2023-12-12
  • 使用Java编写导出不确定行数列数数据的工具类

    使用Java编写导出不确定行数列数数据的工具类

    这篇文章主要为大家详细介绍了如何使用Java编写导出不确定行数列数数据的工具类,文中的示例代码讲解详细,感兴趣的小伙伴可以跟随小编一起学习一下
    2024-03-03

最新评论