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实现音乐播放器完整代码(调整显示音量大小、调整进度、图片切换)

    这篇文章主要给大家介绍了关于java实现音乐播放器(调整显示音量大小、调整进度、图片切换)的相关资料,这本身是老师布置的一个作业,写完感觉不错分享给大家,需要的朋友可以参考下
    2023-07-07
  • 三分钟快速掌握Java中枚举(enum)

    三分钟快速掌握Java中枚举(enum)

    enum的全称为enumeration, 是 JDK 1.5中引入的新特性,存放在 java.lang包中。下面这篇文章是我在使用enum过程中的一些经验和总结,分享出来方便大家快速的掌握Java中枚举(enum),有需要的朋友们下面跟着小编来一起看看吧。
    2016-12-12
  • springsecurity 基本使用详解

    springsecurity 基本使用详解

    这篇文章主要介绍了springsecurity 基本使用,本文通过图文并茂的形式给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2020-11-11
  • java中pdf转图片的实现方法

    java中pdf转图片的实现方法

    下面小编就为大家带来一篇java中pdf转图片的实现方法。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2016-12-12
  • Spring Boot如何利用拦截器加缓存完成接口防刷操作

    Spring Boot如何利用拦截器加缓存完成接口防刷操作

    流的需求出现在许多常见的场景中,下面这篇文章主要给大家介绍了关于Spring Boot如何利用拦截器加缓存完成接口防刷操作的相关资料,文中通过实例代码介绍的非常详细,需要的朋友可以参考下
    2022-02-02
  • 在Java中将jsonObject转换成对象的实现方法

    在Java中将jsonObject转换成对象的实现方法

    在现代的Web开发中,JSON作为一种轻量级的数据交换格式,因其易读性和易于解析的特点而被广泛使用,本文将介绍如何在Java中将​​jsonObject​​转换成Java对象,主要通过使用Gson库来实现这一功能,需要的朋友可以参考下
    2025-04-04
  • SpringBoot参数验证10个技巧值得收藏

    SpringBoot参数验证10个技巧值得收藏

    Spring Boot提供了内置的验证注解,可以帮助简单、快速地对输入字段进行验证,例如检查 null 或空字段、强制执行长度限制、使用正则表达式验证模式以及验证电子邮件地址,那么在Spring Boot应用中如何做好参数校验工作呢,本文提供了10个小技巧感兴趣的朋友一起看看吧
    2023-08-08
  • 聊聊Java中接口重试机制的几种解决方案

    聊聊Java中接口重试机制的几种解决方案

    接口请求重试机制是保证系统稳定性和容错能力的重要手段之一,当接口请求发生失败或暂时性错误时,通过重试机制可以提高请求的成功率,本文将详细介绍接口请求重试机制的几种常见方法,感兴趣的可以了解一下
    2025-07-07
  • 使用Mybatis生成树形菜单的方法详解

    使用Mybatis生成树形菜单的方法详解

    开发中我们难免会遇到各种树形结构展示的场景,比如用户登录系统后菜单的展示等,本文为大家整理了使用Mybatis生成树形菜单的方法,感兴趣的小伙伴可以了解一下
    2023-06-06
  • 详解Spring Boot 配置加载顺序及属性加载顺序

    详解Spring Boot 配置加载顺序及属性加载顺序

    这篇文章主要介绍了详解Spring Boot 配置加载顺序及属性加载顺序,本章内容基于 Spring Boot 2.0 进行详解,感兴趣的朋友跟随脚本之家小编一起看看吧
    2018-08-08

最新评论