5

Atomic 原子类与其底层原理 CAS

 2 years ago
source link: https://maxqiu.com/article/detail/129
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Atomic 原子类与其底层原理 CAS
Atomic 原子类与其底层原理 CAS

2022/01/19  Java

示例代码:


上文说道 volatile 关键字 不保证原子性,那么在不使用 synchronized 关键字 的情况下如何解决多线程安全问题呢?就是本文的主角:Atomic原子类

Atomic 中文是原子的意思。在这里指一个操作是不可中断的。即使是在多个线程一起执行的时候,一个操作一旦开始,就不会被其他线程干扰。

所以:原子类 是具有原子/原子操作特征的类。

并发包JUC(java.util.concurrent)的原子类都在java.util.concurrent.atomic

运行如下代码:

  1. public class AtomicTest {
  2. @Test
  3. void test() {
  4. Date date = new Date();
  5. for (int i = 0; i < 10; i++) {
  6. new Thread(() -> {
  7. for (int j = 0; j < 1000; j++) {
  8. date.volatileNumAdd();
  9. date.atomicNumAdd();
  10. }
  11. }, i + "").start();
  12. }
  13. while (Thread.activeCount() > 2) {
  14. // 让出当前线程,给其他线程执行
  15. Thread.yield();
  16. }
  17. System.out.println("over! volatile:\t" + date.volatileNum);
  18. System.out.println("over! atomic:\t" + date.atomicNum);
  19. }
  20. }
  21. class Date {
  22. volatile int volatileNum = 0;
  23. AtomicInteger atomicNum = new AtomicInteger(0);
  24. public void volatileNumAdd() {
  25. this.volatileNum++;
  26. }
  27. public void atomicNumAdd() {
  28. atomicNum.getAndIncrement();
  29. }
  30. }

结果如下:

  1. main over! volatileNum : 7111
  2. main over! atomicNum : 10000
  • 基本类型:使用原子的方式更新基本类型
    • AtomicInteger:整型原子类
    • AtomicLong:长整型原子类
    • AtomicBoolean:布尔型原子类
  • 数组类型:使用原子的方式更新数组里的某个元素
    • AtomicIntegerArray:整型数组原子类
    • AtomicLongArray:长整型数组原子类
    • AtomicReferenceArray:引用类型数组原子类
  • 引用类型:用于包装对象
    • AtomicReference:引用类型原子类
    • AtomicMarkableReference:原子更新带有标记的引用类型。该类将boolean标记与引用关联起来
    • AtomicStampedReference:原子更新带有版本号的引用类型。该类将整数值与引用关联起来,可用于解决原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题
  • 对象的属性修改类型
    • AtomicIntegerFieldUpdater:原子更新整型字段的更新器
    • AtomicLongFieldUpdater:原子更新长整型字段的更新器
    • AtomicReferenceFieldUpdater:原子更新引用类型里的字段

基本类型原子类(以AtomicInteger为例)

  1. public class AtomicIntegerTest {
  2. @Test
  3. void test() {
  4. // 无参构造:默认值为0
  5. // AtomicInteger atomic = new AtomicInteger();
  6. // 有参构造:初始化传入初始值,
  7. AtomicInteger atomic = new AtomicInteger(1);
  8. System.out.println("获取当前值:" + atomic.get());
  9. System.out.println("\n设置值");
  10. atomic.set(5);
  11. System.out.println("新值:" + atomic.get());
  12. System.out.println("\n获取当前值,并设置新值");
  13. System.out.println("原始:" + atomic.getAndSet(2));
  14. System.out.println("新值:" + atomic.get());
  15. System.out.println("\n获取当前值,并增加值");
  16. System.out.println("原始:" + atomic.getAndAdd(2));
  17. System.out.println("新值:" + atomic.get());
  18. System.out.println("\n增加值,并获取结果");
  19. System.out.println("新值:" + atomic.addAndGet(2));
  20. System.out.println("\n获取当前值,并自增");
  21. System.out.println("原始:" + atomic.getAndIncrement());
  22. System.out.println("新值:" + atomic.get());
  23. System.out.println("\n获取当前值,并自减");
  24. System.out.println("原始:" + atomic.getAndDecrement());
  25. System.out.println("新值:" + atomic.get());
  26. System.out.println("\n自增后,获取当前值,相当于 ++i");
  27. System.out.println("新值:" + atomic.incrementAndGet());
  28. System.out.println("\n自减后,获取当前值,相当于 --i");
  29. System.out.println("新值:" + atomic.decrementAndGet());
  30. System.out.println("\n比较并修改值");
  31. int i = atomic.get();
  32. System.out.println("原始:" + i);
  33. System.out.println("修改结果1:" + atomic.compareAndSet(i, 8) + "\t值:" + atomic.get());
  34. System.out.println("修改结果2:" + atomic.compareAndSet(i, 8) + "\t值:" + atomic.get());
  35. System.out.println("\n转换");
  36. System.out.println(atomic.intValue());
  37. System.out.println(atomic.longValue());
  38. System.out.println(atomic.floatValue());
  39. System.out.println(atomic.doubleValue());
  40. }
  41. }
  1. 获取当前值:1
  2. 设置值
  3. 新值:5
  4. 获取当前值,并设置新值
  5. 原始:5
  6. 新值:2
  7. 获取当前值,并增加值
  8. 原始:2
  9. 新值:4
  10. 增加值,并获取结果
  11. 新值:6
  12. 获取当前值,并自增
  13. 原始:6
  14. 新值:7
  15. 获取当前值,并自减
  16. 原始:7
  17. 新值:6
  18. 自增后,获取当前值,相当于 ++i
  19. 新值:7
  20. 自减后,获取当前值,相当于 --i
  21. 新值:6
  22. 比较并修改值
  23. 原始:6
  24. 修改结果1:true 值:8
  25. 修改结果2:false 值:8
  26. 转换
  27. 8
  28. 8
  29. 8.0
  30. 8.0

数组类型原子类(以AtomicIntegerArray为例)

  1. public class AtomicIntegerArrayTest {
  2. @Test
  3. void test() {
  4. // 有参构造:指定数组长度,数组内初始值为0
  5. // AtomicIntegerArray atomic = new AtomicIntegerArray(10);
  6. // 有参构造:传入数组
  7. AtomicIntegerArray atomic = new AtomicIntegerArray(new int[] {1, 2, 3, 4, 5});
  8. System.out.println("数组的长度为:" + atomic.length());
  9. System.out.println("\n获取指定位置的值");
  10. for (int i = 0; i < atomic.length(); i++) {
  11. System.out.println(i + "\t" + atomic.get(i));
  12. }
  13. System.out.println("\n更新指定位置值");
  14. atomic.set(0, 2);
  15. System.out.println("位置:" + 0 + "\t新值:" + atomic.get(0));
  16. System.out.println("\n获取指定位置当前值,并设置新值");
  17. System.out.println("位置:" + 0 + "\t原始:" + atomic.getAndSet(0, 2));
  18. System.out.println("位置:" + 0 + "\t新值:" + atomic.get(0));
  19. System.out.println("\n获取指定位置当前值,并增加值");
  20. System.out.println("位置:" + 0 + "\t原始:" + atomic.getAndAdd(0, 2));
  21. System.out.println("位置:" + 0 + "\t新值:" + atomic.get(0));
  22. System.out.println("\n指定位置增加值,并获取结果");
  23. System.out.println("位置:" + 0 + "\t新值:" + atomic.addAndGet(0, 2));
  24. System.out.println("\n获取指定位置当前值,并自增");
  25. System.out.println("位置:" + 0 + "\t原始:" + atomic.getAndIncrement(0));
  26. System.out.println("位置:" + 0 + "\t新值:" + atomic.get(0));
  27. System.out.println("\n获取指定位置当前值,并自减");
  28. System.out.println("位置:" + 0 + "\t原始:" + atomic.getAndDecrement(0));
  29. System.out.println("位置:" + 0 + "\t新值:" + atomic.get(0));
  30. System.out.println("\n指定位置自增后,获取当前值,相当于 ++i");
  31. System.out.println("位置:" + 0 + "\t新值:" + atomic.incrementAndGet(0));
  32. System.out.println("\n指定位置自减后,获取当前值,相当于 --i");
  33. System.out.println("位置:" + 0 + "\t新值:" + atomic.decrementAndGet(0));
  34. System.out.println("\n指定位置比较并修改值");
  35. int i = atomic.get(0);
  36. System.out.println("位置:" + 0 + "\t原始:" + i);
  37. System.out.println("位置:" + 0 + "\t修改结果1:" + atomic.compareAndSet(0, i, 8) + "\t值:" + atomic.get(0));
  38. System.out.println("位置:" + 0 + "\t修改结果2:" + atomic.compareAndSet(0, i, 8) + "\t值:" + atomic.get(0));
  39. }
  40. }
  1. 数组的长度为:5
  2. 获取指定位置的值
  3. 0 1
  4. 1 2
  5. 2 3
  6. 3 4
  7. 4 5
  8. 更新指定位置值
  9. 位置:0 新值:2
  10. 获取指定位置当前值,并设置新值
  11. 位置:0 原始:2
  12. 位置:0 新值:2
  13. 获取指定位置当前值,并增加值
  14. 位置:0 原始:2
  15. 位置:0 新值:4
  16. 指定位置增加值,并获取结果
  17. 位置:0 新值:6
  18. 获取指定位置当前值,并自增
  19. 位置:0 原始:6
  20. 位置:0 新值:7
  21. 获取指定位置当前值,并自减
  22. 位置:0 原始:7
  23. 位置:0 新值:6
  24. 指定位置自增后,获取当前值,相当于 ++i
  25. 位置:0 新值:7
  26. 指定位置自减后,获取当前值,相当于 --i
  27. 位置:0 新值:6
  28. 指定位置比较并修改值
  29. 位置:0 原始:6
  30. 位置:0 修改结果1:true 值:8
  31. 位置:0 修改结果2:false 值:8

引用类型原子类

引用类型用于修改对象,对象示例如下:

  1. @Getter
  2. @Setter
  3. @NoArgsConstructor
  4. @AllArgsConstructor
  5. @ToString
  6. public class Person {
  7. private String name;
  8. private Integer age;
  9. }

AtomicReference

  1. public class AtomicReferenceTest {
  2. @Test
  3. void test() {
  4. // 无参构造:默认null
  5. // AtomicReference<Person> atomic = new AtomicReference<>();
  6. // 有参构造:传入初始对象
  7. AtomicReference<Person> atomic = new AtomicReference<>(new Person("张三", 25));
  8. System.out.println("获取当前值:" + atomic.get());
  9. System.out.println("\n设置值");
  10. atomic.set(new Person("李四", 30));
  11. System.out.println("新值:" + atomic.get());
  12. System.out.println("\n获取当前值,并设置新值");
  13. System.out.println("原始:" + atomic.getAndSet(new Person("王五", 35)));
  14. System.out.println("新值:" + atomic.get());
  15. System.out.println("\n比较并修改值");
  16. Person oldP = atomic.get();
  17. Person newP = new Person("赵六", 40);
  18. System.out.println("原始:" + oldP);
  19. System.out.println("修改结果1:" + atomic.compareAndSet(oldP, newP) + "\t值:" + atomic.get());
  20. System.out.println("修改结果2:" + atomic.compareAndSet(oldP, newP) + "\t值:" + atomic.get());
  21. }
  22. }
  1. 获取当前值:Person(name=张三, age=25)
  2. 设置值
  3. 新值:Person(name=李四, age=30)
  4. 获取当前值,并设置新值
  5. 原始:Person(name=李四, age=30)
  6. 新值:Person(name=王五, age=35)
  7. 比较并修改值
  8. 原始:Person(name=王五, age=35)
  9. 修改结果1:true 值:Person(name=赵六, age=40)
  10. 修改结果2:false 值:Person(name=赵六, age=40)

AtomicMarkableReference

  1. public class AtomicMarkableReferenceTest {
  2. @Test
  3. void test() {
  4. // 有参构造:初始化对象和标记
  5. AtomicMarkableReference<Person> atomic = new AtomicMarkableReference<>(new Person("张三", 18), false);
  6. System.out.println("取值:");
  7. System.out.println("对象:" + atomic.getReference());
  8. System.out.println("标记:" + atomic.isMarked());
  9. System.out.println("\n获取当前值并同时将标记存储在boolean类型的数组中(数组长度至少为1)");
  10. boolean[] arr = new boolean[1];
  11. System.out.println("对象:" + atomic.get(arr));
  12. System.out.println("标记:" + arr[0]);
  13. System.out.println("\n设置值和标记");
  14. Person person = new Person("李四", 30);
  15. atomic.set(person, true);
  16. System.out.println("新对象:" + atomic.getReference());
  17. System.out.println("新标记:" + atomic.isMarked());
  18. System.out.println("\n当对象相同时,单独修改标记");
  19. System.out.println("原标记:" + atomic.isMarked());
  20. System.out.println("修改结果:" + atomic.attemptMark(person, false));
  21. System.out.println("新标记:" + atomic.isMarked());
  22. System.out.println("\n比较并修改值");
  23. Person oldP = atomic.getReference();
  24. boolean oldF = atomic.isMarked();
  25. Person newP = new Person("王五", 45);
  26. boolean newF = false;
  27. System.out.println("修改结果1:" + atomic.compareAndSet(oldP, newP, oldF, newF));
  28. System.out.println("对象:" + atomic.getReference());
  29. System.out.println("标记:" + atomic.isMarked());
  30. System.out.println("修改结果2:" + atomic.compareAndSet(oldP, newP, oldF, newF));
  31. System.out.println("对象:" + atomic.getReference());
  32. System.out.println("标记:" + atomic.isMarked());
  33. }
  34. }
  1. 取值:
  2. 对象:Person(name=张三, age=18)
  3. 标记:false
  4. 获取当前值并同时将标记存储在boolean类型的数组中(数组长度至少为1)
  5. 对象:Person(name=张三, age=18)
  6. 标记:false
  7. 设置值和标记
  8. 新对象:Person(name=李四, age=30)
  9. 新标记:true
  10. 当对象相同时,单独修改标记
  11. 原标记:true
  12. 修改结果:true
  13. 新标记:false
  14. 比较并修改值
  15. 修改结果1:true
  16. 对象:Person(name=王五, age=45)
  17. 标记:false
  18. 修改结果2:false
  19. 对象:Person(name=王五, age=45)
  20. 标记:false

AtomicReference

  1. public class AtomicStampedReferenceTest {
  2. @Test
  3. void test() {
  4. // 有参构造:初始化对象和标记
  5. AtomicStampedReference<Person> atomic = new AtomicStampedReference<>(new Person("张三", 18), 1);
  6. System.out.println("取值:");
  7. System.out.println("对象:" + atomic.getReference());
  8. System.out.println("版本:" + atomic.getStamp());
  9. System.out.println("\n获取当前值并同时将标记存储在boolean类型的数组中(数组长度至少为1)");
  10. int[] arr = new int[1];
  11. System.out.println("对象:" + atomic.get(arr));
  12. System.out.println("版本:" + arr[0]);
  13. System.out.println("\n设置值和版本");
  14. Person person = new Person("李四", 30);
  15. atomic.set(person, 2);
  16. System.out.println("新对象:" + atomic.getReference());
  17. System.out.println("新版本:" + atomic.getStamp());
  18. System.out.println("\n当对象相同时,单独修改版本");
  19. System.out.println("原版本:" + atomic.getStamp());
  20. System.out.println("修改结果:" + atomic.attemptStamp(person, 3));
  21. System.out.println("新版本:" + atomic.getStamp());
  22. System.out.println("\n比较并修改值");
  23. Person oldP = atomic.getReference();
  24. int oldV = atomic.getStamp();
  25. Person newP = new Person("王五", 45);
  26. int newV = 4;
  27. System.out.println("修改结果1:" + atomic.compareAndSet(oldP, newP, oldV, newV));
  28. System.out.println("对象:" + atomic.getReference());
  29. System.out.println("版本:" + atomic.getStamp());
  30. System.out.println("修改结果2:" + atomic.compareAndSet(oldP, newP, oldV, newV));
  31. System.out.println("对象:" + atomic.getReference());
  32. System.out.println("版本:" + atomic.getStamp());
  33. }
  34. }
  1. 取值:
  2. 对象:Person(name=张三, age=18)
  3. 版本:1
  4. 获取当前值并同时将标记存储在boolean类型的数组中(数组长度至少为1)
  5. 对象:Person(name=张三, age=18)
  6. 版本:1
  7. 设置值和版本
  8. 新对象:Person(name=李四, age=30)
  9. 新版本:2
  10. 当对象相同时,单独修改版本
  11. 原版本:2
  12. 修改结果:true
  13. 新版本:3
  14. 比较并修改值
  15. 修改结果1:true
  16. 对象:Person(name=王五, age=45)
  17. 版本:4
  18. 修改结果2:false
  19. 对象:Person(name=王五, age=45)
  20. 版本:4

对象的属性修改器

如果需要原子更新某个类里的某个字段时,需要用到对象的属性修改原子类。

  1. public class AtomicFieldUpdaterTest {
  2. @Test
  3. void test() {
  4. // 整形字段的更新器
  5. // 使用静态方法创建更新器,第一个参数为对象类型,第二个参数为要更新的字段名称,该字段必须修饰为 public volatile
  6. AtomicIntegerFieldUpdater<User1> integerUpdater = AtomicIntegerFieldUpdater.newUpdater(User1.class, "age");
  7. User1 user1 = new User1("Java", 22);
  8. // 获取当前对象被管理字段的值并原子自增
  9. System.out.println(integerUpdater.getAndIncrement(user1));
  10. // 获取当前对象被管理字段的值
  11. System.out.println(integerUpdater.get(user1));
  12. // 引用类型字段的更新器
  13. // 使用静态方法创建更新器,第一个参数为对象类型,第二个参数为要更新的字段类型,第三个参数为要更新的字段名称,该字段必须修饰为 public volatile
  14. AtomicReferenceFieldUpdater<User2, Integer> referenceUpdater = AtomicReferenceFieldUpdater.newUpdater(User2.class, Integer.class, "age");
  15. User2 user2 = new User2("Jerry", 18);
  16. // 获取当前对象被管理字段的值并设置新值
  17. System.out.println(referenceUpdater.getAndSet(user2, 20));
  18. // 获取当前对象被管理字段的值
  19. System.out.println(referenceUpdater.get(user2));
  20. }
  21. }
  22. @Getter
  23. @Setter
  24. @NoArgsConstructor
  25. @AllArgsConstructor
  26. class User1 {
  27. private String name;
  28. public volatile int age;
  29. }
  30. @Getter
  31. @Setter
  32. @NoArgsConstructor
  33. @AllArgsConstructor
  34. class User2 {
  35. private String name;
  36. public volatile Integer age;
  37. }
  1. 22
  2. 23
  3. 18
  4. 20

AtomicInteger部分底层源码

  1. public class AtomicInteger extends Number implements java.io.Serializable {
  2. // setup to use Unsafe.compareAndSwapInt for updates
  3. private static final Unsafe unsafe = Unsafe.getUnsafe();
  4. private static final long valueOffset;
  5. static {
  6. try {
  7. valueOffset = unsafe.objectFieldOffset
  8. (AtomicInteger.class.getDeclaredField("value"));
  9. } catch (Exception ex) { throw new Error(ex); }
  10. }
  11. private volatile int value;
  12. /**
  13. * Creates a new AtomicInteger with the given initial value.
  14. *
  15. * @param initialValue the initial value
  16. */
  17. public AtomicInteger(int initialValue) {
  18. value = initialValue;
  19. }
  20. /**
  21. * Atomically increments by one the current value.
  22. *
  23. * @return the previous value
  24. */
  25. public final int getAndIncrement() {
  26. return unsafe.getAndAddInt(this, valueOffset, 1);
  27. }
  28. }
  1. Unsafe:是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该
    类可以直接操作特定内存的数据。Unsafe类存在于sun.misc包中,其内部方法操作可以像C的指针一样直接操作内存,因为JavaCAS操作的执行依赖于Unsafe类的方法。注意:Unsafe类中的所自方法都是native修饰的。也就是说Unsafe类中的方法都直接调用操作系统底层资源执行相应任务
  2. valueOffset:表示该变量值在内存中的偏移地址,因为Unsafe就是根据内存偏移地址获取数据的。
  3. value:变量用volatile修饰,保证了多线程之间的内存可见性。
  4. getAndIncrement()AtomicInteger.getAndIncrement()Unsafe.getAndAddInt()传入了三个值
    1. this:当前对象
    2. valueOffset:当前对象的内存地址
    3. 1:添加的值

Unsafe.getAndAddInt(Object var1, long var2, int var4)源码

  1. public final int getAndAddInt(Object var1, long var2, int var4) {
  2. int var5;
  3. do {
  4. var5 = this.getIntVolatile(var1, var2);
  5. } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
  6. return var5;
  7. }
  1. 首先this.getIntVolatile(var1, var2)获取当前对象var1在内存地址var2的快照值var5
  2. 假设获取快照值之后过了一段时间
  3. 然后this.compareAndSwapInt(var1, var2, var5, var5 + var4)获取当前对象var1在内存地址var2的真实值并和快照值var5进行比较
    1. 相同:表示这段时间这个对象的真实值没有改变,那么更新为期望值var5 + var4并返回true,取反为false后跳出当前循环,最后返回原值var5
    2. 不同:表示这段时间这个对象的真实值发生改变,那么返回false,取反为true后再次进入循环重新取值和比较,直至完成

再举个例子:假设线程A和线程B两个线程同时执行getAndAddInt操作

  1. AtomicInteger里面的value原始值均为3,即主内存中AtomicIntegervalue为3,根据JMM模型,线程A和线程B各自持有一份值为3的value的副本分别到各自的工作内存。
  2. 线程A通过getIntVolatile(var1, var2)拿到value值3,这时线程A被挂起。
  3. 线程B通过getIntVolatile(var1, var2)拿到value值3,此时刚好线程B没有被挂起并执行compareAndSwapInt方法比较内存值也为3,成功修改内存值为4,线程B打完收工,一切OK。
  4. 这时线程A恢复,执行compareAndSwapInt方法比较,发现自己的值3和主内存的值4不一致,说明该值已经被其它线程修改过了,那A线程本次修改失败,只能重新读取重新来一遍了。
  5. 线程A重新获取value值,因为变量valuevolatile修饰,所以其它线程对它的修改,线程A总是能够看到,线程A继续执行compareAndSwapInt进行比较替换,直到成功。

CAS的全称为Compare-And-Swap, 它是一条CPU并发原语。它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的。

CAS并发原语体现在JAVA语言中就是sun.misc.Unsafe类中的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令。这是一种完全依赖于硬件的功能,通过它实现了原子操作。再次强调,由于CAS是一种系统原语,原语属于操作系统用语范畴,是由若干条指令组成的,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,也就是说CAS是一条CPU的原子指令,不会造成所谓的数据不一致问题。

Unsafe类中的compareAndSwapInt是一个本地方法,该方法的实现位于unsafe.cpp中。下文是部分源码,详见:http://hg.openjdk.java.net/jdk8/jdk8/hotspot/file/tip/src/share/vm/prims/unsafe.cpp

  1. UNSAFE_ENTRY(jboolean, Unsafe_CompareAndSwapInt(JNIEnv *env, jobject unsafe, jobject obj, jlong offset, jint e, jint x))
  2. UnsafeWrapper("Unsafe_CompareAndSwapInt");
  3. oop p = JNIHandles::resolve(obj);
  4. jint* addr = (jint *) index_oop_from_field_offset_long(p, offset);
  5. return (jint)(Atomic::cmpxchg(x, addr, e)) == e;
  6. UNSAFE_END

先拿到变量value在内存中的地址。再通过Atomic::cmpxchg实现比较替换,其中参数x是即将更新的值,参数e是原内存的值。

  1. 循环时间长开销很大:源码中有一个do while,如果CAS一直失败,会给CPU带来很大开销
  2. 只能保证一个共享变量的原子操作:对多个共享变量操作时,无法保证操作的原子性
  3. 会出现ABA问题

ABA的问题解决

ABA问题的产生

CAS算法实现一个重要前提是需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差类会导致数据的变化。比如说线程A、线程B从内存中取出V1,然后线程B进行了一些操作将值变成了V2然后又进行一些操作将值变回V1,这时候线程A进行CAS操作发现内存中仍然是V1,然后线程A操作成功。尽管线程A的CAS操作成功,但是不代表这个过程就是没有问题的。

ABA问题的解决

只需要使用带有版本控制的原子类AtomicStampedReference即可

  1. public class ABATest {
  2. /**
  3. * 普通的对象引用原子类,不能解决ABA问题
  4. */
  5. @Test
  6. void atomicReference() {
  7. AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
  8. new Thread(() -> {
  9. atomicReference.compareAndSet(100, 101);
  10. atomicReference.compareAndSet(101, 100);
  11. }, "t1").start();
  12. new Thread(() -> {
  13. // 暂停一会t2线程,保证上面的t1线程完成了一次ABA操作
  14. try {
  15. TimeUnit.SECONDS.sleep(1);
  16. } catch (InterruptedException e) {
  17. e.printStackTrace();
  18. }
  19. System.out.println(atomicReference.compareAndSet(100, 2019));
  20. }, "t2").start();
  21. // 等待线程结束
  22. while (Thread.activeCount() > 2) {
  23. // 让出当前线程,给其他线程执行
  24. Thread.yield();
  25. }
  26. System.out.println(atomicReference.get());
  27. }
  28. /**
  29. * 带版本控制的对象引用原子类,可以解决ABA的问题
  30. */
  31. @Test
  32. void atomicStampedReference() {
  33. AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(100, 1);
  34. new Thread(() -> {
  35. // 获取初始版本
  36. int stamp = atomicStampedReference.getStamp();
  37. System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);
  38. // 暂停一会t1线程,保证t2线程获取到了初始版本号
  39. try {
  40. TimeUnit.SECONDS.sleep(1);
  41. } catch (InterruptedException e) {
  42. e.printStackTrace();
  43. }
  44. atomicStampedReference.compareAndSet(100, 101, stamp, ++stamp);
  45. System.out.println(Thread.currentThread().getName() + "\t第2次版本号:" + atomicStampedReference.getStamp());
  46. atomicStampedReference.compareAndSet(101, 100, stamp, ++stamp);
  47. System.out.println(Thread.currentThread().getName() + "\t第3次版本号:" + atomicStampedReference.getStamp());
  48. }, "t1").start();
  49. new Thread(() -> {
  50. // 获取初始版本
  51. int stamp = atomicStampedReference.getStamp();
  52. System.out.println(Thread.currentThread().getName() + "\t第1次版本号:" + stamp);
  53. // 暂停一会t2线程,保证上面的t3线程完成了一次ABA操作
  54. try {
  55. TimeUnit.SECONDS.sleep(3);
  56. } catch (InterruptedException e) {
  57. e.printStackTrace();
  58. }
  59. boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, ++stamp);
  60. System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + result);
  61. }, "t2").start();
  62. // 等待线程结束
  63. while (Thread.activeCount() > 2) {
  64. // 让出当前线程,给其他线程执行
  65. Thread.yield();
  66. }
  67. System.out.println("当前最新实际版本号:" + atomicStampedReference.getStamp() + "\t当前实际最新值:" + atomicStampedReference.getReference());
  68. }
  69. }

不带版本控制的原子类AtomicReference会出现ABA问题

  1. true
  2. 2019

携带版本控制的原子类AtomicStampedReference可以解决ABA问题

  1. t1 第1次版本号:1
  2. t2 第1次版本号:1
  3. t1 第2次版本号:2
  4. t1 第3次版本号:3
  5. t2 修改成功否:false
  6. 当前最新实际版本号:3 当前实际最新值:100

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK