CAS 出现 ABA 问题,就是在多线程的场景下,一个线程把值由 A 改成 B,再由 B 改成 A,其他线程读取该变量的时候依然是 A,无感知该变量的修改,这里引入AtomicStampedReference通过版本号来解决这个问题。
单线程下AtomicStampedReference通过版本号来解决 ABA 问题。
/**
* @author
* @descpription: 解决CAS的ABA问题
* @date 2024/6/11
*/
public class AtomicStampedReferenceDemo {
/**
* 单线程下通过版本号解决ABA问题
* @param args
*/
public static void main(String[] args) {
Book javaBook = new Book(1, "python");
AtomicStampedReference<Book> atomicStampedReference =
new AtomicStampedReference<>(javaBook, 1);
System.out.println("初始数据:" + atomicStampedReference.getReference());
System.out.println("初始版本号:" + atomicStampedReference.getStamp());
Book mysqlBook = new Book(2, "python");
boolean b = atomicStampedReference.compareAndSet(javaBook, mysqlBook, 1, 2);
System.out.println("修改结果:" + b);
System.out.println("修改后的数据:" + atomicStampedReference.getReference());
System.out.println("修改后的版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(mysqlBook, javaBook, 2, 3);
System.out.println("修改回初始数据:" + atomicStampedReference.getReference());
System.out.println("修改回初始版本号:" + atomicStampedReference.getStamp());
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
class Book{
private int id;
private String bookName;
}
下面通过 2 个案例来说明一下:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicStampedReference;
/**
* @descpription: AtomicInteger出现ABA问题
* AtomicStampedReference通过版本号解决这个问题
* @date 2024/6/11
*/
public class ABADemo {
static AtomicInteger atomicInteger = new AtomicInteger(100);
static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<Integer>(100, 1);
public static void main(String[] args) {
abdHappenVersion();
}
/**
* 通过版本号解决了ABA,多线程通过版本号来判断是否发生了ABA问题
*/
public static void abdHappenVersion(){
new Thread(()-> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp);
try {TimeUnit.MILLISECONDS.sleep(500);} catch (InterruptedException e) {throw new RuntimeException(e);}
atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第二次版本号:" + atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1);
System.out.println(Thread.currentThread().getName() + "\t第三次版本号:" + atomicStampedReference.getStamp());
},"t3").start();
new Thread(()-> {
int stamp = atomicStampedReference.getStamp();
System.out.println(Thread.currentThread().getName() + "\t第一次版本号:" + stamp);
// 暂停1秒钟t4线程,保证t3线程完成一次ABA
try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {throw new RuntimeException(e);}
//由于版本号导致无法修改
boolean a = atomicStampedReference.compareAndSet(100, 2022, stamp, stamp + 1);
System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + a + "\t当前最新实际版本号:" + stamp);
},"t4").start();
}
/**
* 无版本号出现ABA问题
*/
public static void abdHappenNoVersion(){
new Thread(() -> {
atomicInteger.compareAndSet(100, 120);
try {TimeUnit.MILLISECONDS.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}
atomicInteger.compareAndSet(120, 100);
},"t1").start();
new Thread(() -> {
try {TimeUnit.MILLISECONDS.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}
//无版本号的约定,可以修改
atomicInteger.compareAndSet(100, 2022);
System.out.println("修改后的值: " + atomicInteger.get());
},"t2").start();
}
}