小白都能看懂的CAS基本原理与实战应用指南

引言

最近有个读者问我什么是CAS,今天了不起来聊聊CAS(Compare And Swap)这个概念。

它在日常编程中非常有用,特别是在多线程编程中。

本篇文章将从原理介绍、源码分析、实战应用等方面进行阐述。

一、CAS原理介绍

CAS(Compare And Swap)是一种用于实现无锁并发算法的技术。

大多数时候它也还有其他叫法:无锁优化、自旋、乐观锁

它的核心思想是:通过比较当前需要修改的值与预期原来的值,如果相等,则使用新值进行替换。

这个过程是原子性的,它底层是靠C语言依赖的操作系统的原子操作来保证原子性的,即在这个过程中不会被其他线程打断。

在Java中,CAS操作主要是通过 java.util.concurrent.atomic包中的原子类来实现的,如 AtomicIntegerAtomicLong等。

原理示例

假设有一个整数变量 count,初始值为0。现在有两个线程A和B同时对 count进行加1操作。使用CAS操作可以确保 count最终的值为2。

  1. 线程A读取 count的值,得到0。
  2. 线程B读取 count的值,得到0。
  3. 线程A将 count的值与预期值0进行比较,相等,则将 count的值更新为1。
  4. 线程B将 count的值与预期值0进行比较,不相等(因为已经被线程A更新为1),则重试。
  5. 线程B重新读取 count的值,得到1。
  6. 线程B将 count的值与预期值1进行比较,相等,则将 count的值更新为2。

最终,count的值为2,实现了无锁并发操作。

二、源码分析

以 java.util.concurrent.atomic.AtomicInteger为例,这个类提供了一个原子的整数值,可以用于实现无锁的整数操作。我们来看一下它的 compareAndSet方法的源码:

public final boolean compareAndSet(int expect, int update){
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}

AI 代码解读

这里的 unsafe是一个 sun.misc.Unsafe类型的对象,它提供了底层的CAS操作。valueOffset是一个静态常量,表示 AtomicInteger内部的 value字段的内存偏移量。expect是预期值,update是新值。这个方法会返回一个布尔值,表示操作是否成功。

小结:

atomicXXX类
  • Compare and Set/Swap 比较并且设定
  • cas(V,Expected,NewValue)
  • if V == EV == New otherwise try again or fail
  • CPU原语支持 atomic底层调用到UnSafe这个方法,三个参数V就是当前版本值,Expected期望值,NewValue赋予的新值。
    只有当V等于E的时候才将新的值赋给这个变量,又因为它是原语支持是CPU级别的,是一个原子操作,所以在设值时不会有其他线程来插队设值。

三、实战应用

我们来看一个实际的例子,如何使用 AtomicInteger实现一个无锁的计数器。

public class Counter {
    /*volatile*/ //int count1 = 0;
    AtomicInteger count = new AtomicInteger(0);
    /*synchronized*/ void m() {
        for (int i = 0; i < 10000; i++)
            //if count1.get() < 1000
            count.incrementAndGet(); //count1++
    }
    public static void main(String[] args) {
        Counter t = new Counter();
        List<Thread> threads = new ArrayList<Thread>();
        for (int i = 0; i < 10; i++) {
            threads.add(new Thread(t::m, "thread-" + i));
        }
        threads.forEach((o) -> o.start());
        threads.forEach((o) -> {
            try {
                o.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        System.out.println(t.count);
    }
}

AI 代码解读

在这个例子中,我们创建了一个 Counter类,内部使用 AtomicInteger实现了无锁的计数,然后我们创建了10个线程,分别对计数器进行增加操作。最后我们输出计数器的值,可以看到它确实是10000,证明我们的无锁计数器是正确的。

四、CAS在日常中的应用场景

在实际开发中,我们可能会遇到以下几种使用CAS的场景:

  1. 无锁计数器 :如上面的例子所示,我们可以使用CAS实现一个高效的无锁计数器,避免了使用同步锁带来的性能开销。
  2. 单例模式 :我们可以使用CAS实现一种线程安全的单例模式,确保在多线程环境下只创建一个实例。
  3. 并发容器 :在实现高性能的并发容器时,如 ConcurrentHashMap,我们可以使用CAS操作来实现无锁或低锁的数据结构更新。
  4. 多线程并发任务 :在多线程并发执行任务时,我们可以使用CAS操作来确保任务状态的正确更新,例如实现一个无锁的任务分发器。

五、ABA问题

如果另一个线程修改V值,假设原来是A,先修改成B,再修改回成A。

当前线程的CAS操作无法分辨当前V值是否发生过变化,这个就是ABA问题。

解决:

  • 在CAS的时候加版本号,每次操作比较下版本号
  • 加 version
  • A 1.0
  • B 2.0
  • A 3.0
  • cas(version)
  • 原子类 AtomicStampedReference解决ABA问题
  • ABA问题重要不?如果是基本数据类型结果没影响,如果是引用对象就不好说了,比如你的女朋友和你复合,前面经过了多少个其他XXX,你觉得有影响不?

六、总结

了不起带着大家从原理介绍、源码分析、实战应用等方面讲解了CAS的相关知识。

通过本文的学习,相信你们已经对CAS有了一定的了解,掌握了如何在实际开发中应用CAS来解决并发问题。

当然,CAS并不是万能的,它也有一定的局限性,例如ABA问题。

在实际开发中,我们需要根据具体场景选择合适的并发策略。

小白都能看懂的CAS基本原理与实战应用指南-阿里云开发者社区

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值