CAS原理分析, 以及引发的ABA问题

CAS 是AtomicInteger类中compareAndSet()方法的缩写 

import java.util.concurrent.atomic.AtomicInteger;

/**
 * CAS 是compareAndSet的缩写 译:比较并交换 此操作是在内存中进行
 */
public class CASTest {
    public static void main(String[] args) {
        //设定初始值1
        AtomicInteger atomicInteger = new AtomicInteger(1);
        /**
         * expect 预期值 update更新值
         * 如果当前值 ==为预期值,则将该值原子设置为给定的更新值。 否则不更新
         */
        atomicInteger.compareAndSet(1,2);
        System.out.println(atomicInteger.get());

        atomicInteger.compareAndSet(2,6);
        System.out.println(atomicInteger.get());
    }
}
compareAndSet()方法是java用来操作计算机内存的 ,为什么这么说呢,看一下源码
AtomicInteger类
public final boolean compareAndSet(int expect, int update) {
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }
private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }
 private volatile int value;
因为java无法直接操作计算机内存,这里创建了一个Unsafe对象,Unsafe对象里都是native方法用来操作C++,而C++可以操作内存
valueOffset = unsafe.objectFieldOffset //获取获取内存地址的偏移值
private volatile int value; 具体的值value是由volatile 来保证里面的值避免指令重排

源码举例:

//调用getAndIncrement()方法 来执行 ++ 操作
atomicInteger.getAndIncrement();

 方法内部三个参数,分别是this(当前对象)valueOffset内存地址, 值1

public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

对应this =  var1,valueOffset = var2, var4 = 1;

这里进行了 do while 自旋锁 

把当前对象地址的值 放到了var5中

如果var1 var2 中的值是 当前var5的值 那么就进行 当前值var5 +1的操作

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;
}
总结 :

CAS:比较当前工作内存张红的值和主内存的值,如果这个值是期望的,那么则执行操作,如果不是就一直循环

在synchronized阻塞算法中,性能上有了很大的提升,高效的解决了原子操作

缺点:

1.循环会耗时,开销大

2.一次只能保证一个共享变量的原子性

3.ABA问题

什么是ABA问题 (类似狸猫换太子的故事)

public class CASTest {
    public static void main(String[] args) {
        //设定初始值1
        AtomicInteger atomicInteger = new AtomicInteger(1);

        //假设线程A 把1改成了2 然后又改成了1
        atomicInteger.compareAndSet(1,2);
        System.out.println(atomicInteger.get());
        atomicInteger.compareAndSet(2,1);
        System.out.println(atomicInteger.get());
        //线程B根本不知道这个1 已经不是原来的值了 ,已经被动过手脚了
        atomicInteger.compareAndSet(1,6);
        System.out.println(atomicInteger.get());
    }
}

如何解决呢 

使用带版本好的原子类AtomicStampedReference

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

/**
 * CAS 是compareAndSet的缩写 译:比较并交换 此操作是在内存中进行
 */
public class CASTest {
    public static void main(String[] args) {
        //设定初始参考值1 initialStamp初始印记1  这里的initialStamp就是一个标记, 类似于乐观锁中的版本号
        AtomicStampedReference<Integer> asr = new AtomicStampedReference<>(1, 1);

        new Thread(() -> {
            int stamp = asr.getStamp();
            System.out.println(Thread.currentThread().getName() + "A1" + stamp);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            /**
             * expectedReference 预期值
             * newReference 新值
             * expectedStamp 当前标记
             * newStamp 新标记
             */
            asr.compareAndSet(1, 2, asr.getStamp(), asr.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "A2" + asr.getStamp());
            asr.compareAndSet(2, 1, asr.getStamp(), asr.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "A3" + asr.getStamp());
        }, "A").start();

        new Thread(() -> {
            int stamp = asr.getStamp();
            System.out.println(Thread.currentThread().getName() + "B1" + asr.getStamp());
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            asr.compareAndSet(1, 6, asr.getStamp(), asr.getStamp() + 1);
            System.out.println(Thread.currentThread().getName() + "B2" + asr.getStamp());
        }, "B").start();
    }
}
这里遇到一个坑

Integer的值过大 ,会造成程序的预期结果不一样

Integer使用了对象缓存机制, 默认范围是-128~-127, 推荐使用静态工厂方法valueOF获取对象实例, 而不是new 因为valueOf使用了缓存,而new一定会创建新的对象分配新的内存空间

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

feng_zi-06

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值