引言
最近有个读者问我什么是CAS,今天了不起来聊聊CAS(Compare And Swap)这个概念。
它在日常编程中非常有用,特别是在多线程编程中。
本篇文章将从原理介绍、源码分析、实战应用等方面进行阐述。
一、CAS原理介绍
CAS(Compare And Swap)是一种用于实现无锁并发算法的技术。
大多数时候它也还有其他叫法:无锁优化、自旋、乐观锁
它的核心思想是:通过比较当前需要修改的值与预期原来的值,如果相等,则使用新值进行替换。
这个过程是原子性的,它底层是靠C语言依赖的操作系统的原子操作来保证原子性的,即在这个过程中不会被其他线程打断。
在Java中,CAS操作主要是通过 java.util.concurrent.atomic
包中的原子类来实现的,如 AtomicInteger
、AtomicLong
等。
原理示例
假设有一个整数变量 count
,初始值为0。现在有两个线程A和B同时对 count
进行加1操作。使用CAS操作可以确保 count
最终的值为2。
- 线程A读取
count
的值,得到0。 - 线程B读取
count
的值,得到0。 - 线程A将
count
的值与预期值0进行比较,相等,则将count
的值更新为1。 - 线程B将
count
的值与预期值0进行比较,不相等(因为已经被线程A更新为1),则重试。 - 线程B重新读取
count
的值,得到1。 - 线程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 == E
V == 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的场景:
- 无锁计数器 :如上面的例子所示,我们可以使用CAS实现一个高效的无锁计数器,避免了使用同步锁带来的性能开销。
- 单例模式 :我们可以使用CAS实现一种线程安全的单例模式,确保在多线程环境下只创建一个实例。
- 并发容器 :在实现高性能的并发容器时,如
ConcurrentHashMap
,我们可以使用CAS操作来实现无锁或低锁的数据结构更新。 - 多线程并发任务 :在多线程并发执行任务时,我们可以使用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问题。
在实际开发中,我们需要根据具体场景选择合适的并发策略。
希望本文能对大家有所帮助,和了不起一起持续学习。