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

简介: 小白都能看懂的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);
}

这里的 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,你觉得有影响不?

六、总结

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

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

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

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

相关文章
|
6月前
|
设计模式 存储 安全
Java的第十三篇文章——JAVA多线程(后期再学一遍)
Java的第十三篇文章——JAVA多线程(后期再学一遍)
|
8月前
|
小程序 Java 容器
“马士兵老师”纯手写2022版多线程与高并发PDF学习手册
这份2022版多线程与高并发的学习手册,是基于同学们2022上半年面试遇到的问题呀以及当下最流行的技术进行的更新升级的版本讲解,从最基础的线程、纤程、协程等等开始讲起,其中还有高并发调优的经验讲解,从基础到精通一站式为大家解决完。
|
11月前
|
存储 开发者
彻底搞懂函数,读这篇文章就够了
如果你之前使用过任何一门编程语言,那么对于你来讲想必已经知道什么是函数,以及如何使用函数了,那你大可不必往下读了。这篇文章是写给新手看的,也就是说我假设你对于函数没有任何的概念。 我们就先从什么是函数来说起吧!
73 0
|
存储 缓存 安全
并发编程原理扫盲笔记
垃圾回收,顾名思义,便是将已经分配出去的,但却不再使用的内存回收回来,以便能够再次分配。在 Java 虚拟机的语境下,垃圾指的是死亡的对象所占据的堆空间。
105 0
|
存储 安全 Java
彻底理解 volatile 关键字及应用场景,面试必问,小白都能看懂!
彻底理解 volatile 关键字及应用场景,面试必问,小白都能看懂!
395 0
彻底理解 volatile 关键字及应用场景,面试必问,小白都能看懂!
|
Web App开发 存储 移动开发
​Cookie 从入门到进阶:一文彻底弄懂其原理以及应用
​Cookie 从入门到进阶:一文彻底弄懂其原理以及应用
464 0
​Cookie 从入门到进阶:一文彻底弄懂其原理以及应用
|
缓存 NoSQL 安全
写给小白看的分布式锁教程(一) 基本概念与使用
写给小白看的分布式锁教程(一) 基本概念与使用
写给小白看的分布式锁教程(一) 基本概念与使用
|
算法 Java 数据库
面试官:如何实现一个乐观锁(小白都能看得懂的代码)
java多线程中的锁分类多种多样,其中有一种主要的分类方式就是乐观和悲观进行划分的。这篇文章主要介绍如何自己手写一个乐观锁代码。不过文章为了保证完整性,会从基础开始介绍。
471 0
面试官:如何实现一个乐观锁(小白都能看得懂的代码)
|
存储 缓存 安全
多线程一 入门序言
多线程一 入门序言
100 0
|
存储 安全 Java
(十六)关于Java多线程锁的升级原理,这篇文章会让你另有收获
对象头用于存储对象的元数据信息,包括运行时数据和类型指针、实例数据存储的是真正有效数据、对齐填充主要补充字节,使得内存所占字节能被8整除。