CAS学习+ABA问题

CAS学习+ABA问题

CAS

定义

比较和交换 (Compare And Swap)它是一条cpu并发原语 是多线程同步的原子指令

作用

判断内存中某个位置的值是否是预期值 如果是就更改为新的值

Unsafe类中getAndAddInt()方法

public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        var5 = this.getIntVolatile(var1, var2);
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

    return var5;
}
//var1 AtomicInteger对象本身
//var2 该对象的引用地址
//var5 根据var1 var2获取到的主物理内存当中的值
//var4 要变动的数量

我们来模拟一个线程A和线程 B同时执行getAndAddInt()的情况

  1. AtomicInteger里的初始value为3 即就是主物理内存当中的值为3 这是线程A和线程B 同时拷贝了一份value 值3 到了自己的工作内存

  2. var5 = this.getIntVolatile(var1, var2);线程A通过这句代码 拿到了value3 到自己的工作内存

此时线程A被挂起(也就是此时的期望值是3)

  1. 线程B也执行 this.getIntVolatile(var1, var2) 这句拿到value 3 同时继续用compareAndSwapInt(var1, var2, var5, var5 + var4)这个CAS方法将3 加一 修改为4 并将4返回到了主物理内存

  2. 这是回到线程A A线程执行compareAndSwapInt这个方法比较 此时通过var1和var2拿到的主物理内存的值4已经与3不相同了 那么将不会修改值 将在进入do这一段代码里 拿到最新的主物理内存当中的值 并进行比较直到更新成功

小总结

扫描二维码关注公众号,回复: 11067204 查看本文章

在我看来 CAS是一种思想

就是先获取主物理内存中的值作为期望值 然后我们再去通过var1 和 var2 获得此时主物理内存的真实值 如果期望值与真实值一致 我们就进行修改 否则 就一直取值比较 直到成功

ABA问题

我们看一下ABA问题的产生

在这里插入图片描述

package com.robot;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

public class ABADemo {
    static AtomicReference<Integer> atomicReference=new AtomicReference<>(100);

    public static void main(String[] args) {
       new Thread(()->{
           //模拟ABA问题
           atomicReference.compareAndSet(100,101);
           atomicReference.compareAndSet(101,100);
       },"a").start();

       new Thread(()->{
           //确保线程a先执行发生了aba问题
           try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
           System.out.println(atomicReference.compareAndSet(100,103));
           System.out.println(atomicReference.get());
       },"b").start();
    }
}

在这里插入图片描述

ABA问题的解决

目前在JDK的atomic包里提供了一个类AtomicStampedReference(带版本号的原子引用类)来解决ABA问题。这个类的compareAndSet方法作用是首先检查当前引用是否等于预期引用,并且当前标志是否等于预期标志,如果全部相等,则以原子方式将该引用和该标志的值设置为给定的更新值

package com.robot;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicStampedReference;

public class ABADemo2 {
    static AtomicStampedReference<Integer> atomicStampedReference=new AtomicStampedReference<Integer>(100,1);

    public static void main(String[] args) {
        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();
            //暂停一秒 确保t2拿到第一次的版本号
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"第一次版本号"+stamp);
            atomicStampedReference.compareAndSet(100,101,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"第二次版本号"+atomicStampedReference.getStamp());
            atomicStampedReference.compareAndSet(101,100,atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
            System.out.println(Thread.currentThread().getName()+"第三次版本号"+atomicStampedReference.getStamp());
        },"t1").start();

        new Thread(()->{
            int stamp=atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName()+"第一次版本号"+stamp);
            //暂停3秒 确保t1执行了ABA问题
            try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); }
            //此时期望的版本号是1 但是最新的版本号已经是3 所以修改不成功
            System.out.println( atomicStampedReference.compareAndSet(100,2020,stamp,10));
            System.out.println("当前最新版本号 "+atomicStampedReference.getStamp());
            System.out.println("当期最新值"+atomicStampedReference.getReference());
        },"t2").start();
    }
}

在这里插入图片描述

发布了39 篇原创文章 · 获赞 19 · 访问量 1478

猜你喜欢

转载自blog.csdn.net/weixin_44222272/article/details/105218115