CAS 表示比较并交换(Compare and Swap),ABA 表示原子化的 ABA 问题,即在多线程环境下,当一个值在操作之前和之后都没有发生变化,但是期间发生了一些其他的变化,导致出现了 ABA 问题。为了解决这些问题,Java 提供了 Atomic 类型和 AtomicReference 类型。
Atomic 类型提供了对基本数据类型的原子化操作,例如 AtomicInteger 和 AtomicLong 类型可以对 int 和 long 类型进行原子化的加、减、乘、除等操作。
AtomicReference 类型提供了对对象类型的原子化操作,例如 AtomicReference 类型可以对任意对象进行原子化的设置、获取、比较和交换操作。
import java.util.concurrent.atomic.AtomicInteger;
/**
* 使用了 AtomicInteger 类型,先将其初始化为 2022,然后使用 compareAndSet 方法进行原子化的比较和交换操作,
* 将其值从 2022 修改为 2023。输出结果为 true 2023,说明原子化的比较和交换操作执行成功。
* @author Administrator
*/
public class MyCAS {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2022);
boolean result = atomicInteger.compareAndSet(2022, 2023);
System.out.println(result + "\t" + atomicInteger.get());
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 有两个线程 t1 和 t2。线程 t1 通过两次 CAS 操作将原子引用的值先改为 2023,再改回 2022,
* 模拟了 ABA 问题的发生。线程 t2 在线程 t1 执行完成后,先获取原子引用的当前值和版本号,模拟了一些其他的操作,
* 然后尝试进行 CAS 操作,将值从当前值加 999。由于线程 t2 并没有使用带有版本号的 CAS 操作,
* 因此它无法识别出 ABA 问题的存在,从而导致 CAS 操作成功,输出结果为 true 3021。
* @author Administrator
*/
public class MyABA {
static AtomicInteger atomicInteger = new AtomicInteger(2022);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
atomicInteger.compareAndSet(2022, 2023);
atomicInteger.compareAndSet(2023, 2022);
System.out.println(Thread.currentThread().getName() + "\t已经完成一次 ABA 操作");
}, "t1").start();
// 保证 t1 先执行完一次 ABA 操作
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
// 获取当前的值和版本号
int expect = atomicInteger.get();
// 模拟进行一次其他的操作
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 尝试进行 CAS 操作
boolean result = atomicInteger.compareAndSet(expect, expect + 999);
System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + result + "\t当前实际值:" + atomicInteger.get());
}, "t2").start();
TimeUnit.SECONDS.sleep(3);
System.out.println("最终结果:" + atomicInteger.get());
}
}
/**
* 有两个线程 t1 和 t2。线程 t1 使用带有版本号的 CAS 操作将原子引用的值先改为 2023,再改为 2022。
* 线程 t2 在线程 t1 执行完成后,使用带有版本号的 CAS 操作将原子引用的值从 2022 改为 2024,并打印出修改的结果、当前版本号和当前值。
* 由于线程 t2 使用了带有版本号的 CAS 操作,因此它能够正确地识别出 ABA 问题的存在,输出结果为 false 2022。
* @author Administrator
*/
public class MyABAResolver {
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(2022, 1);
public static void main(String[] args) throws InterruptedException {
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
atomicStampedReference.compareAndSet(2022, 2023, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第2次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(2023, 2022, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第3次版本号:" + atomicStampedReference.getStamp());
}, "t1").start();
new Thread(() -> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
boolean result = atomicStampedReference.compareAndSet(2022, 2024, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + result + "\t当前版本号:" + atomicStampedReference.getStamp() + "\t当前实际值:" + atomicStampedReference.getReference());
}, "t2").start();
TimeUnit.SECONDS.sleep(5);
System.out.println("最终结果:" + atomicStampedReference.getReference());
}
}
AtomicStampedReference 是 Java 中的一个原子类型,它是基于版本号的原子引用。在使用 AtomicStampedReference 解决 ABA 问题时,版本号可以用来识别对象是否发生了 ABA 问题。
AtomicStampedReference 内部维护了两个字段,分别是 private volatile V reference; 和 private volatile int stamp;,用来存储引用对象和版本号。
下面是 AtomicStampedReference 类的部分源代码解析:
public class AtomicStampedReference<V> {
private volatile V reference; // 存储引用对象
private volatile int stamp; // 存储版本号
public AtomicStampedReference(V initialRef, int initialStamp) {
reference = initialRef;
stamp = initialStamp;
}
// 获取引用对象
public V getReference() {
return reference;
}
// 获取版本号
public int getStamp() {
return stamp;
}
// 带版本号的 CAS 操作
public boolean compareAndSet(V expectedReference, V newReference,
int expectedStamp, int newStamp) {
synchronized (this) {
if (expectedReference == reference && expectedStamp == stamp) {
reference = newReference;
stamp = newStamp;
return true;
} else {
return false;
}
}
}
// 其他方法...
}
构造方法 AtomicStampedReference(V initialRef, int initialStamp) 用来初始化引用对象和版本号。getReference() 方法用来获取引用对象,getStamp() 方法用来获取版本号。
compareAndSet(V expectedReference, V newReference, int expectedStamp, int newStamp) 方法是 AtomicStampedReference 类最重要的方法之一。在执行 compareAndSet 操作时,如果 expectedReference 和 expectedStamp 分别与 reference 和 stamp 相等,就会将 reference 和 stamp 更新为 newReference 和 newStamp。它用来进行带版本号的 CAS 操作,并且在操作成功时返回 true,操作失败时返回 false。这个方法是线程安全的,使用了同步锁 synchronized 来保证线程安全。
总的来说,AtomicStampedReference 类的实现基于版本号的原子性引用类型,通过版本号的更新和 CAS 操作的比较,保证了 ABA 问题的解决。同时,由于版本号是内置的,使用起来比自定义版本号解决器更加方便。