CAS如何解决ABA问题

点赞再看,养成习惯,微信搜索「小大白日志」关注这个搬砖人。

文章不定期同步公众号,还有各种一线大厂面试原题、我的学习系列笔记。

CAS如何解决ABA问题

什么是ABA:在CAS过程中,线程1、线程2分别从内存中拿到了当前值为A,,同时线程2把当前值A改为B,又把B该回来变为A,此后线程1拿到的仍为A,导致线程1执行cas成功,但这个过程却发生了ABA问题,现场资源可能和当初不一样了(线程2把当前值由A->B->A)

解决方法版本号机制,利用版本号标记线程1拿到的‘当前值’的版本,若线程2进行了A->B->A操作,则版本号会改变,那线程1再次拿到的‘当前值’的版本和第一次的肯定是不同的,从而判定cas失败;

java代码中AtomicStampedReference类的cas方法实现了版本号机制,可用它来解决ABA问题:

/**
 * @Description 测试解决CAS中的ABA问题
 * @Author afei
 * @date:2021/6/27
 */
public class AtomicStampedReferenceTest {
    //AtomicInteger和AtomicStampedReference的初始值都为100,但AtomicStampedReference带了版本号0
    private static AtomicInteger atomicInt = new AtomicInteger(100);
    private static AtomicStampedReference atomicStampedRef = new AtomicStampedReference(100, 0);
    public static void main(String[] args) {
        //会产生ABA问题
        aba1();
        //用AtomicStampedReference解决cas过程中的ABA问题
        aba2();
 }

 public static void aba1(){
     Thread t1 = new Thread(new Runnable() {
         @Override
         public void run() {
             boolean c3 = atomicInt.compareAndSet(100, 101);
             System.out.println(c3); // true
         }
     });
     Thread t2 = new Thread(new Runnable() {
         @Override
         public void run() {
             //t2改变100->101->100,compareAndSet的参数:期待的值,新值
             atomicInt.compareAndSet(100, 101);
             atomicInt.compareAndSet(101, 100);
         }
     });
     t1.start();
     t2.start();
     try {
         //t1,t2执行完,主线程才能继续执行
         t1.join();
         t2.join();
     } catch (InterruptedException e) {
         e.printStackTrace();
     }
 }
  public static void aba2(){
      Thread t1 = new Thread(new Runnable() {
          @Override
          public void run() {
              int stamp = atomicStampedRef.getStamp();
              try {
                  TimeUnit.SECONDS.sleep(2);
              } catch (InterruptedException e) {
              }
              //compareAndSet中四个参数分别为:期待的值,新值,期待的版本号,新的版本号
              boolean c3 = atomicStampedRef.compareAndSet(100, 101, stamp, stamp + 1);
              System.out.println(c3); // false
          }
      });
      Thread t2 = new Thread(new Runnable() {
          @Override
          public void run(){
              try {
                  TimeUnit.SECONDS.sleep(1);
              } catch (InterruptedException e) {
              }
              //t2改变100->101->100,compareAndSet中四个参数分别为:期待的值,新值,期待的版本号,新的版本号
              atomicStampedRef.compareAndSet(100, 101, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
              atomicStampedRef.compareAndSet(101, 100, atomicStampedRef.getStamp(), atomicStampedRef.getStamp() + 1);
          }
      });
      t1.start();
      t2.start();
  }
}

AtomicInteger的原理

Java可以利用Syschronized、ReentrantLock、AtomicInteger类实现线程安全,AtomicInteger封装了一个【volatile int value】属性,它可以对这个属性进行许多原子性操作,这些原子性操作大多是基于cas原理,而在cas中,AtomicInteger使用的是一个叫Unsafe的类中的方法,Unsafe可以提供一些底层操作,也就是CPU特定的指令集,进而避免了并发问题(Unsafe是一个很危险的类,它可以做一些和内存相关的操作)

OK,如果文章哪里有错误或不足,欢迎各位留言。
创作不易,各位的「三连」是二少创作的最大动力!我们下期见!

上一篇:Codeforces Round #647 (Div. 2) D


下一篇:一文看懂KMP(看毛片)算法