#1024程序员节|征文#
CAS是什么?
CAS(Compare And Swap),即比较与交换,是一种乐观锁的实现方式,用于在不使用锁的情况下实现多线程之间的变量同步。
CAS操作包含三个操作数:内存位置(V)、预期原值(A)和新值(B)。
当且仅当内存位置的值与预期原值相等时,才会将该位置的值更新为新值。
这种机制广泛应用于Java并发编程中,如AtomicInteger、AtomicLong等原子类内部都使用了CAS操作来实现线程安全。
CAS存在的问题
尽管CAS在并发编程中具有重要作用,但它也存在一些显著的问题:
- ABA问题:
- 问题描述:CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是,如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化(仍为A),但实际上该值已经被其他线程修改过。
- 解决思路:通过在变量前面添加版本号,每次变量更新的时候都把版本号加一。这样,即使变量的值从A变为B再变回A,版本号也会改变,从而CAS能够识别出变量的值已经发生了变化。Java从1.5开始提供了AtomicStampedReference类来解决ABA问题。
- 循环时间长开销大:
- 问题描述:CAS操作如果长时间不成功,会导致其一直自旋(即不断重试),给CPU带来非常大的开销。特别是在高并发场景下,多个线程同时对一个变量进行CAS操作,可能导致大量线程长时间处于自旋状态。
- 解决思路:可以通过自旋锁和pause指令优化。例如,JVM支持处理器提供的pause指令,该指令能让自旋失败的线程CPU睡眠一小段时间再继续自旋,从而减少CPU的浪费。
- 只能保证一个共享变量的原子操作:
- 问题描述:CAS操作只能保证对单个共享变量的原子操作。当需要对多个共享变量进行原子操作时,CAS无法保证这些操作的原子性。
- 解决思路:对于多个共享变量的原子操作,需要使用其他技术如锁(synchronized或ReentrantLock)来保证原子性。Java从1.5开始提供了AtomicReference类来保证引用对象之间的原子性,可以将多个变量放在一个对象里来进行CAS操作。
示例讲解
CAS(Compare And Swap),即比较并交换,是一种用于实现多线程同步的乐观锁机制。在Java中,CAS操作广泛应用于java.util.concurrent.atomic包下的原子类中,如AtomicInteger、AtomicLong等。以下将给出一个具体的CAS示例并进行讲解。
CAS示例
假设有一个场景,我们需要实现一个线程安全的计数器,多个线程同时对这个计数器进行自增操作。
我们可以使用AtomicInteger类来实现这个计数器,而AtomicInteger内部正是通过CAS操作来保证线程安全的。
import java.util.concurrent.atomic.AtomicInteger;
public class CounterExample {
private static AtomicInteger counter = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
// 创建多个线程对counter进行自增操作
Thread t1 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.incrementAndGet();
}
}, "Thread-1");
Thread t2 = new Thread(() -> {
for (int i = 0; i < 10000; i++) {
counter.incrementAndGet();
}
}, "Thread-2");
t1.start();
t2.start();
// 等待所有线程执行完毕
t1.join();
t2.join();
// 打印最终结果
System.out.println("Final counter value: " + counter.get());
}
}
CAS讲解
在上述示例中,AtomicInteger类的incrementAndGet()方法实现了线程安全的自增操作。
这个方法内部正是通过CAS操作来保证的。
具体来说,incrementAndGet()方法的大致实现逻辑如下:
- 获取当前值:首先,通过get()方法或类似机制获取当前计数器的值,假设为oldValue。
- 计算新值:然后,基于当前值oldValue计算出新值newValue,即newValue = oldValue + 1。
- CAS操作:接着,使用CAS操作尝试将计数器的值从oldValue更新为newValue。CAS操作包含三个参数:内存地址(这里是计数器的内存地址)、预期原值(oldValue)、新值(newValue)。如果此时计数器的实际值仍然等于oldValue,则将计数器的值更新为newValue,操作成功;如果不等于,则操作失败,说明在获取当前值和进行CAS操作之间,计数器的值已经被其他线程修改了。
- 循环重试:如果CAS操作失败,则重新执行步骤1到步骤3,直到CAS操作成功为止。这个过程称为自旋。
- 返回新值:CAS操作成功后,返回更新后的新值newValue。