# CAS-ABA 问题
# 一。概述:
ABA 问题是在多线程并发的情况下,发生的一种现象。上一次记录了有关 CAS 操作的一些知识,CAS 通过比较内存中的一个数据是否是预期值,如果是就将它修改成新值,如果不是则进行自旋,重复比较的操作,直到某一刻内存值等于预期值再进行修改。而 ABA 问题则是在 CAS 操作中存在的一个经典问题,这个问题某些时候不会带来任何影响,某些时候却是影响很大的。
# 二。什么是 ABA 问题?
# 理解一:
当执行 campare and swap 会出现失败的情况。例如,一个线程先读取共享内存数据值 A,随后因某种原因,线程暂时挂起,同时另一个线程临时将共享内存数据值先改为 B,随后又改回为 A。随后挂起线程恢复,并通过 CAS 比较,最终比较结果将会无变化。这样会通过检查,这就是 ABA 问题。 在 CAS 比较前会读取原始数据,随后进行原子 CAS 操作。这个间隙之间由于并发操作,最终可能会带来问题。
# 理解二:
“ABA” 问题:假设 t1 线程工作时间为 10 秒,t2 线程工作时间为 2 秒,那么可能在 A 的工作期间,主内存中的共享变量 A 已经被 t2 线程修改了多次,只是恰好最后一次修改的值是该变量的初始值,虽然用 CAS 判定出来的结果是期望值,但是却不是原来那个了 =======》“狸猫换太子”
相当于是只关心共享变量的起始值和结束值,而不关心过程中共享变量是否被其他线程动过。
有些业务可能不需要关心中间过程,只要前后值一样就行,但是有些业务需求要求变量在中间过程不能被修改。
只靠 CAS 无法保证 ABA 问题,需要使用 “原子引用” 才能解决!!!!
# 三.ABA 问题的解决:
# 原子引用:(存在 ABA 问题)
案例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 package InterviewTest; import java.util.concurrent.atomic.AtomicReference;class User { String name; int age; public User (String name,int age) { this .name=name; this .age=age; } @Override public String toString () { return "User [name=" + name + ", age=" + age + "]" ; } public String getName () { return name; } public void setName (String name) { this .name = name; } public int getAge () { return age; } public void setAge (int age) { this .age = age; } } public class AtomicReferenceDemo { public static void main (String[] args) { User z3 = new User ("z3" ,25 ); User li4 = new User ("li4" ,25 ); AtomicReference<User> atomicReference = new AtomicReference <>(); atomicReference.set(z3); System.out.println(atomicReference); System.out.println(atomicReference.compareAndSet(z3, li4)+ " " +atomicReference.get().toString()); System.out.println(atomicReference.compareAndSet(li4, z3)+ " " +atomicReference.get().toString()); } }
# 带版本号的原子引用(解决 ABA 问题)
AtomicStampedReference 版本号原子引用:
案例:两种原子引用的对比
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 package InterviewTest;import java.util.concurrent.atomic.AtomicReference;import java.util.concurrent.atomic.AtomicStampedReference;public class ABADemo { static AtomicReference<Integer> atomicReference = new AtomicReference <>(100 ); static AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference <>(100 ,1 ); public static void main (String[] args) { System.out.println("************以下是ABA问题的产生**************" ); new Thread (()->{ atomicReference.compareAndSet(100 , 101 ); atomicReference.compareAndSet(101 , 100 ); },"t1" ).start(); new Thread (()->{ try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100 , 2019 ) +" " +atomicReference.get()); },"t2" ).start(); try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("************以下是ABA问题的解决**************" ); new Thread (()->{ int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() +" " +" 第一次版本号:" +stamp); try { Thread.sleep(1000 ); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100 , 101 , atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1 ); System.out.println(Thread.currentThread().getName() +" " +" 第2次版本号:" +atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101 , 100 , atomicStampedReference.getStamp(), atomicStampedReference.getStamp()+1 ); System.out.println(Thread.currentThread().getName() +" " +" 第3次版本号:" +atomicStampedReference.getStamp()); },"t3" ).start(); new Thread (()->{ int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() +" " +" 第一次版本号:" +stamp); try { Thread.sleep(3000 ); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = atomicStampedReference.compareAndSet( 100 , 2019 , stamp, stamp+1 ); System.out.println(Thread.currentThread().getName()+ " 修改成功否:" +result+" 当前最新实际版本号:" +atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName()+ " 当前实际最新值:" +atomicStampedReference.getReference()); },"t4" ).start(); } } 输出: ************以下是ABA问题的产生************** true 2019 ************以下是ABA问题的解决************** t3 第一次版本号:1 t4 第一次版本号:1 t3 第2 次版本号:2 t3 第3 次版本号:3 t4 修改成功否:false 当前最新实际版本号:3 t4 当前实际最新值:100
# 关于我
Brath 是一个热爱技术的 Java 程序猿,公众号「InterviewCoder」定期分享有趣有料的精品原创文章!
非常感谢各位人才能看到这里,原创不易,文章如果有帮助可以关注、点赞、分享或评论,这都是对我的莫大支持!