java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > java CAS原理

Java CAS原理和用法总结

作者:看透也说透kevin

CAS(Compare And Swap)是一种无锁的原子操作机制,通过一条CPU硬件指令(如x86架构的CMPXCHG指令)实现,能保证操作的原子性,本文给大家介绍java CAS原理和用法,感兴趣的朋友跟随小编一起看看吧

一、CAS 原理

1. 核心思想

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

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

2. 操作模型

CAS 操作涉及三个操作数:

伪代码逻辑如下:

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

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

3. 工作流程

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

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

4. 优点与缺点

二、Java 中的 CAS 用法

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

1. 主要的原子类

2. 核心方法

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

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原理内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文