欢迎光临散文网 会员登陆 & 注册

CAS是什么?

2023-06-29 21:16 作者:红红的2  | 我要投稿

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

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

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

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

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

原理示例

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

java面试大全:kdocs.cn/l/cfoqiIIJ4xmW

  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); }

这里的 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);    } }


编辑搜图

在这个例子中,我们创建了一个 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,你觉得有影响不?

面试大全超级详细!!


java面试大全:


搜索公众号回复eee003获取


CAS是什么?的评论 (共 条)

分享到微博请遵守国家法律