Junior Engineer浅谈CAS

系列文章目录

文章目录


一、乐观锁


1、CAS

CAS是一种原子操作(不可被中断的操作),用于实现多线程环境下的同步控制,基本逻辑是:

c 复制代码
boolean compareAndSwap(int expectedValue, int newValue)

比较当前值是否等于expectedValue

如果相等,则将当前值更新为newValue

返回是否成功,true表示更新成功

CAS的作用

CAS是实现无锁和乐观锁的基础,避免了传统锁带来的性能问题,比如线程阻塞,死锁等

2、示例

1、java中的CAS操作是通过sun.misc.unsafe类提供的,这个类提供了底层的内存操作能力

但是unsafe类是一个不公开的API, 因此java提供了更高层的封装,比如AtomicInteger, AtomicLong等

c 复制代码
import java.util.concurrent.atomic.AtomicInteger;

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

        // 尝试将值从 0 改为 1
        boolean success = atomicInteger.compareAndSet(0, 1);
        System.out.println("First CAS: " + success); // true

        // 再次尝试将值从 0 改为 2(此时实际值是 1)
        success = atomicInteger.compareAndSet(0, 2);
        System.out.println("Second CAS: " + success); // false

        // 使用 getAndIncrement(内部使用 CAS)
        int value = atomicInteger.getAndIncrement();
        System.out.println("After increment: " + value); // 1
    }
}

2、手动实现CAS

c 复制代码
public class SimpleCAS {
    private volatile int value;

    public SimpleCAS(int initialValue) {
        this.value = initialValue;
    }

    public synchronized boolean compareAndSet(int expected, int update) {
        if (value == expected) {
            value = update;
            return true;
        }
        return false;
    }

    public int getValue() {
        return value;
    }

    public static void main(String[] args) {
        SimpleCAS cas = new SimpleCAS(0);

        System.out.println(cas.compareAndSet(0, 1)); // true
        System.out.println(cas.compareAndSet(0, 2)); // false
        System.out.println(cas.getValue()); // 1
    }
}

注意上面compareAndSet是用synchronized 实现的,这仍然是锁机制,不是真正的CAS

3、ABA问题以及解决方法

1、什么是ABA问题

假设变量 x 的值是 A,然后被修改为 B,再被改回 A。

如果你使用普通的 CAS(如 AtomicInteger),它会认为 x 的值没有变,但实际上中间经历了变化。

这可能导致错误的逻辑判断。

2、如何ABA

AtomicStampedReference 通过引入一个版本号(stamp),每次修改时都更新这个版本号,从而可以检测到这种变化

c 复制代码
package practice.cas讲解;

import java.util.concurrent.atomic.AtomicStampedReference;

public class ABACase {
    public static void main(String[] args) {
        //初始值为 0,版本号为 0
        AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(0, 0);
//Java 是按值传递的,如果你传一个 int 变量进去,函数内部修改它不会影响外部的变量。
//因此,我们使用一个 int[] 数组来包装 int,这样可以在方法内部修改数组中的值,外部也能看到。
        int[] stamp = {0};

        // 第一次修改
        //如果期望值和版本号都匹配,则修改成功
        //如果当前值是0,版本号是0,则将值改为1,版本号改为1

        boolean success = ref.compareAndSet(0, 1, 0, 1);
        System.out.println("First CAS: " + success); // true

        // 第二次修改(ABA 问题)
        //当前值是1,版本号是1,尝试将值改回0,版本号改为2
        //此时值从 1 变成 0,但之前曾经是 0。这就是 ABA 问题的体现。
        success = ref.compareAndSet(1, 0, 1, 2);
        System.out.println("Second CAS: " + success); // true

        // 第三次修改(使用正确的 stamp)
        //当前值是0,版本号是2,尝试将值从0改为3,版本号改为3
        //什么情况可以返回false?
        //如果当前值不是0,或者版本号不是2,则修改失败,返回false
        //因为版本号已经变了,说明中间有其他修改发生过
        success = ref.compareAndSet(0, 1, 2, 3);
        System.out.println("Third CAS: " + success); // true
    }
}

3、多线程示例,重现ABA问题

虽然两个线程都"修改"了值,但最终值还是 0。这说明 AtomicInteger 无法检测到 ABA 问题

c 复制代码
import java.util.concurrent.atomic.AtomicInteger;

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

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                int current = value.get();
                if (current == 0) {
                    // 假设这里有一些操作
                    value.compareAndSet(0, 1);
                    System.out.println("T1: Set to 1");
                    try {
                        Thread.sleep(10); // 模拟延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    value.compareAndSet(1, 0);
                    System.out.println("T1: Set back to 0");
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                int current = value.get();
                if (current == 0) {
                    // 假设这里有一些操作
                    value.compareAndSet(0, 1);
                    System.out.println("T2: Set to 1");
                    try {
                        Thread.sleep(10); // 模拟延迟
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    value.compareAndSet(1, 0);
                    System.out.println("T2: Set back to 0");
                }
            }
        });

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

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Final value: " + value.get());
    }
}

每次修改都会更新版本号(stamp),即使值回到原点,也能识别出变化。

c 复制代码
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABASolution {
    public static void main(String[] args) {
        AtomicStampedReference<Integer> ref = new AtomicStampedReference<>(0, 0);

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                int[] stamp = {0};
                Integer current = ref.get(stamp);
                if (current == 0) {
                    boolean success = ref.compareAndSet(0, 1, 0, 1);
                    if (success) {
                        System.out.println("T1: Set to 1 with stamp 1");
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        success = ref.compareAndSet(1, 0, 1, 2);
                        if (success) {
                            System.out.println("T1: Set back to 0 with stamp 2");
                        }
                    }
                }
            }
        });

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 100; i++) {
                int[] stamp = {0};
                Integer current = ref.get(stamp);
                if (current == 0) {
                    boolean success = ref.compareAndSet(0, 1, 0, 1);
                    if (success) {
                        System.out.println("T2: Set to 1 with stamp 1");
                        try {
                            Thread.sleep(10);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        success = ref.compareAndSet(1, 0, 1, 2);
                        if (success) {
                            System.out.println("T2: Set back to 0 with stamp 2");
                        }
                    }
                }
            }
        });

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

        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        int[] finalStamp = {0};
        Integer finalValue = ref.get(finalStamp);
        System.out.println("Final value: " + finalValue + ", stamp: " + finalStamp[0]);
    }
}
相关推荐
m0_6356474813 小时前
信号与槽已经使用connect语句连接,并且参数也匹配,但是发送信号以后不执行槽函数?
开发语言·qt
十年一梦实验室13 小时前
【AI解析】一个用 C#编写的类,用于通过以太网非过程命令与 Keyence CV-X 系列视觉系统进行通信
开发语言·c#
啊森要自信13 小时前
【 GUI自动化测试】GUI自动化测试(一) 环境安装与测试
开发语言·python·ui·单元测试·pytest
Never_Satisfied13 小时前
在JavaScript / HTML中,让<audio>元素中的多个<source>标签连续播放
开发语言·javascript·html
失散1313 小时前
分布式专题——15 ZooKeeper特性与节点数据类型详解
java·分布式·zookeeper·云原生·架构
ThisIsMirror13 小时前
Spring的三级缓存如何解决单例Bean循环依赖
java·spring·缓存
love530love13 小时前
EPGF架构:Python开发的长效稳定之道
开发语言·ide·人工智能·windows·python·架构·pycharm
菠菠萝宝13 小时前
【Java八股文】12-分布式面试篇
java·分布式·zookeeper·面试·seata·redisson
yk1001013 小时前
Spring DefaultSingletonBeanRegistry
java·后端·spring
Metaphor69213 小时前
Java 将 PDF 转换为 HTML:高效解决方案与实践
java·经验分享·pdf·html