深入理解 CAS 与 ABA 问题
目录
引言
1. 什么是 CAS?
CAS 的伪代码
2. CAS 的工作原理
3. CAS 的实现
3.1 AtomicInteger 示例
4. 什么是 ABA 问题?
5. ABA 问题的危害
6. 解决 ABA 问题的方法(原子引用)
6.1 AtomicStampedReference
6.2 AtomicMarkableReference
7. CAS 的优缺点
优点:
缺点:
8. 总结
引言
在并发编程中,CAS(Compare-And-Swap)是一种非常重要的无锁机制,用于实现线程安全。然而,CAS 并非完美无缺,它存在一个经典的问题——ABA 问题。本文将深入探讨 CAS 的工作原理、ABA 问题的成因及其解决方案。
1. 什么是 CAS?
CAS(Compare-And-Swap)是一种原子操作,用于在多线程环境下实现无锁的线程安全。它的核心思想是:
-
比较:检查某个内存位置的值是否与预期值相等。
-
交换:如果相等,则将该内存位置的值更新为新值;否则,不做任何操作。
CAS 操作是原子的,即在执行过程中不会被其他线程打断。
CAS 的伪代码
boolean compareAndSwap(V, A, B) {
if (V == A) {
V = B;
return true;
}
return false;
}
2. CAS 的工作原理
CAS 操作通常涉及三个参数:
-
内存地址(V):需要更新的变量。
-
预期值(A):认为变量当前应该具有的值。
-
新值(B):希望将变量更新为的值。
CAS 的操作步骤如下:
-
读取内存地址 V 的当前值。
-
比较当前值是否等于预期值 A。
-
如果相等,则将内存地址 V 的值更新为新值 B。
-
如果不相等,则操作失败(通常需要重试)。
-
-
返回操作是否成功。
用代码理解下什么是CAS:
package com.kuang;
import java.util.concurrent.atomic.AtomicInteger;
/**
* CAS : 比较并交换 compareAndSet
*
* 参数:期望值,更新值
* public final boolean compareAndSet(int expect, int update) {
* return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
* }
* @author 狂神说Java 24736743@qq.com
*/
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(5);
// main do somethings...
// 期望的是5,后面改为 2020 , 所以结果为 true,2020
System.out.println(atomicInteger.compareAndSet(5,
2020)+"=>"+atomicInteger.get());
// 期望的是5,后面改为 1024 , 所以结果为 false,2020
System.out.println(atomicInteger.compareAndSet(5,
1024)+"=>"+atomicInteger.get());
}
}
一句话:真实值和期望值相同,就修改成功,真实值和期望值不同,就修改失败!
3. CAS 的实现
在 Java 中,CAS 操作是通过 Unsafe
类或 java.util.concurrent.atomic
包中的原子类(如 AtomicInteger
)实现的。
3.1 AtomicInteger
示例
import java.util.concurrent.atomic.AtomicInteger;
public class CASExample {
private AtomicInteger value = new AtomicInteger(0);
public void increment() {
int oldValue;
int newValue;
do {
oldValue = value.get(); // 获取当前值
newValue = oldValue + 1; // 计算新值
} while (!value.compareAndSet(oldValue, newValue)); // CAS 更新
}
public int getValue() {
return value.get();
}
}
CAS 的缺点
1、循环时间长开销很大。
可以看到源码中存在 一个 do...while 操作,如果CAS失败就会一直进行尝试。
2、只能保证一个共享变量的原子操作。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。但是:
对多个共享变量操作时,循环CAS就无法保证操作的原子性,这时候就可以用锁来保证原子性。
3、引出来 ABA 问题???
提出问题 => 原子类 AtomicInteger 的ABA问题谈谈?原子更新引用知道吗?
CAS => UnSafe => CAS 底层思想 => ABA => 原子引用更新 => 如何规避ABA问题
4. 什么是 ABA 问题?
ABA 问题是 CAS 操作中的一个经典问题。它描述的是以下场景:
-
线程 1 读取变量 V 的值为 A。
-
线程 1 被挂起。
-
线程 2 将变量 V 的值从 A 修改为 B。
-
线程 2 再次将变量 V 的值从 B 修改回 A。
-
线程 1 恢复执行,发现变量 V 的值仍然是 A,认为没有变化,于是 CAS 操作成功。
尽管 CAS 操作成功,但实际上变量 V 的值已经经历了从 A 到 B 再到 A 的变化。这种变化可能会导致程序逻辑错误。
5. ABA 问题的危害
ABA 问题在某些场景下可能会导致严重的后果。例如:
-
链表操作:在无锁链表中,如果一个节点的值从 A 变为 B 又变回 A,CAS 操作可能会错误地认为链表没有变化,从而导致数据丢失或链表损坏。
-
资源管理:在资源池中,如果一个资源的状态从“空闲”变为“使用中”又变回“空闲”,CAS 操作可能会错误地认为资源一直处于“空闲”状态。
6. 解决 ABA 问题的方法(原子引用)
为了解决 ABA 问题,可以引入版本号或时间戳机制。每次更新变量时,不仅更新值,还更新版本号或时间戳。这样,即使值相同,版本号或时间戳不同,CAS 操作也会失败。(类似乐观锁)
6.1 AtomicStampedReference
Java 提供了 AtomicStampedReference
类来解决 ABA 问题。它通过维护一个版本号(stamp)来避免 ABA 问题。
import java.util.concurrent.atomic.AtomicStampedReference;
public class ABASolution {
private static AtomicStampedReference<Integer> value = new AtomicStampedReference<>(0, 0);
public static void main(String[] args) {
int stamp = value.getStamp(); // 获取当前版本号
Integer oldValue = value.getReference(); // 获取当前值
// 尝试更新值和版本号
boolean success = value.compareAndSet(oldValue, oldValue + 1, stamp, stamp + 1);
if (success) {
System.out.println("更新成功!");
} else {
System.out.println("更新失败!");
}
}
}
6.2 AtomicMarkableReference
AtomicMarkableReference
是另一种解决 ABA 问题的方式,它通过一个布尔标记来区分不同的状态。
import java.util.concurrent.atomic.AtomicMarkableReference;
public class ABASolution {
private static AtomicMarkableReference<Integer> value = new AtomicMarkableReference<>(0, false);
public static void main(String[] args) {
boolean[] markHolder = new boolean[1];
Integer oldValue = value.get(markHolder); // 获取当前值和标记
// 尝试更新值和标记
boolean success = value.compareAndSet(oldValue, oldValue + 1, markHolder[0], !markHolder[0]);
if (success) {
System.out.println("更新成功!");
} else {
System.out.println("更新失败!");
}
}
}
7. CAS 的优缺点
优点:
-
无锁:CAS 不需要加锁,避免了锁带来的性能开销和死锁风险。
-
高性能:在低竞争环境下,CAS 的性能优于锁机制。
-
可扩展性:CAS 适用于高并发场景,能够有效提高程序的并发性能。
缺点:
-
ABA 问题:CAS 只能检查值是否相等,无法感知值的变化过程。
-
自旋开销:在高竞争环境下,CAS 可能需要多次重试,导致 CPU 资源浪费。
-
只能保证一个变量的原子性:CAS 只能对一个变量进行原子操作,无法支持多个变量的复合操作。
8. 总结
CAS 是一种高效的无锁线程安全机制,广泛应用于并发编程中。然而,CAS 存在 ABA 问题,可能导致程序逻辑错误。通过引入版本号或时间戳机制(如 AtomicStampedReference
或 AtomicMarkableReference
),可以有效解决 ABA 问题。