通过看源码,我们发现有个do while,如果CAS失败,会一直进行尝试。如果CAS长时间一直不成功,可能会给CPU带来很大的开销。
当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作。但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候就可用锁来保证原子性。
解决ABA问题只靠CAS不能解决,还需要用到原子引用技术。即AtomicReference
简单代码测试
import lombok.AllArgsConstructor; import lombok.Getter; import lombok.ToString; import java.util.concurrent.atomic.AtomicReference; @Getter @ToString @AllArgsConstructor class User{ String userName; int age; } public class AtomicReferenceDemo { public static void main(String[] args) { User z3 = new User("Z3",22); User li4 = new User("li4",25); AtomicReference<User> atomicReference = new AtomicReference<>(); atomicReference.set(z3); System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString()); System.out.println(atomicReference.compareAndSet(z3, li4)+"\t"+atomicReference.get().toString()); } }编译结果
解决: 原子引用+修改版本号(类似时间戳)
代码:
import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import java.util.concurrent.atomic.AtomicStampedReference; /** * ABA问题的解决: 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 { //暂停一秒钟,保证上面的t1完成了一次ABA操作 TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(atomicReference.compareAndSet(100, 2019) + "\t" + atomicReference.get()); }, "t2").start(); //暂停两秒钟等上面代码执行完 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("=============以下是ABA问题的解决=============="); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp); // 暂停一秒钟t3线程,为了t4线程也能拿到相同的stamp。 try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } atomicStampedReference.compareAndSet(100, 101, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp()); atomicStampedReference.compareAndSet(101, 100, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1); System.out.println(Thread.currentThread().getName() + "\t第2次版本号" + atomicStampedReference.getStamp()); }, "t3").start(); new Thread(() -> { int stamp = atomicStampedReference.getStamp(); System.out.println(Thread.currentThread().getName() + "\t第一次版本号" + stamp); // 暂停3秒钟t4线程,保证上面的t3线程完成了一次ABA操作。 try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } boolean result = atomicStampedReference.compareAndSet(100, 101, stamp, stamp + 1); System.out.println(Thread.currentThread().getName() + "\t修改成功否:" + result + "\t当前最新实际版本号:" + atomicStampedReference.getStamp()); System.out.println(Thread.currentThread().getName() + "\t当前实际最新值" + atomicStampedReference.getReference()); }, "t4").start(); } }编译结果: