CAS底层原理与ABA问题

CAS界说

CAS(Compare And Swap)是一种无锁算法。CAS算法是乐观锁的一种实现。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当预期值A和内存值V相同时,将内存值V修改为B并返回true,否则返回false。

CAS与synchronized

(1)synchronized加锁,统一时间段只允许一个线程接见,能够保证一致性然则并发性下降。

(2)CAS是一个自旋锁算法,使用do-while不停判断(没有加锁),保证一致性和并发性,然则对照消耗CPU资源。使用CAS就可以不用加锁来实现线程平安。

  • 原子性保证:CAS算法依赖于rt.jar包下的sun.misc.Unsafe类,该类中的所有方式都是native修饰的,直接挪用操作系统底层资源执行响应的义务。
  • 内存可见性和克制指令重排序的保证:AtomicXxx类中的成员变量value是由volatile修饰的:private volatile int value;

CAS算法的瑕玷

CAS虽然很高效的解决了原子操作问题,然则CAS仍然存在三大问题。

  • 循环时间长、开销很大。

当某一方式好比:getAndAddInt执行时,若是CAS失败,会一直举行实验。若是CAS长时间实验然则一直不乐成,可能会给CPU带来很大的开销。

  • 只能保证一个共享变量的原子操作。

当操作1个共享变量时,我们可以使用循环CAS的方式来保证原子操作,然则操作多个共享变量时,循环CAS就无法保证操作的原子性,这个时刻就需要用锁来保证原子性。

  • 存在ABA问题

若是一个线程在首次读取时的值为A,并且在赋值的时刻检查该值仍然是A,然则可能在这两次操作,之间有另外一个线程现将变量的值改成了B,然后又将该值改回为A,那么CAS会误认为该变量没有转变过。

CAS底层原理

sum.misc.Unsafe类中有多个方式被native关键字符号,这说明该方式是原生态的方式,它是一个挪用非java语言的接口,也就是说这个接口的实现是其他语言实现的。CAS并发原语就是体现在java的sum.misc.Unsafe类中的各个方式,挪用这个类中的CAS方式JVM就会通过其他语言天生若干条系统指令,完整这些指令的过程中,是不允许被中止的,以是CAS是一条CUP的原子指令,以是它不会造成数据不一致问题。

多线程情况下,number变量每次++都市泛起线程平安问题,AtomicInteger则不会,由于它保证了原子性。

CAS底层原理与ABA问题

 我们进去看,getAndIncrement挪用的就是Unsafe类中的getAndAddInt方式,this示意当前工具,valueOffset示意变量值在内存中的偏移量(也就是内存地址)

想用@Autowired注入static静态成员?官方不推荐你却还偏要这么做

CAS底层原理与ABA问题

我们再进入Unsafe类看看var1就是getAndIncrement方式传过来的工具,var2是系统偏移量,这里是使用了do-while循环,一开始循环就通过var1工具和var2偏移量获取期望值var5,进入循环,compareAndSwapInt方式被native关键字符号的,以是他是原子性的 ,var2的值与var的值相等时,则使用新的值var5+var4,返回true,循环条件取反则竣事循环,否则若是var2与var5不相等就继续循环,直到条件不满足再跳出循环

// unsafe.class
public final int getAndAddInt(Object var1, long var2, int var4) {
    int var5;
    do {
        // 获取工具var1,偏移量为var2地址上的值,并赋值给var5
        var5 = this.getIntVolatile(var1, var2);
        /**
         * 再次获取工具var1,偏移量var2地址上的值,并和var5举行对照:
         * - 若是不相等,返回false,继续执行do-while循环
         * - 若是相等,将返回的var5数值和var4相加并返回
         */
    } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
    // 最终总是返回工具var1,偏移量为var2地址上的值,即上述所说的V。
    return var5;
}

CAS底层原理与ABA问题

ABA问题解决方案

使用AtomicStampedReference或者AtomicMarkableReference来解决CAS的ABA问题,思绪类似于SVN版本号,SpringBoot热部署中trigger.txt

AtomicStampedReference解决方案:每次修改都市让stamp值加1,类似于版本控制号

package com.raicho.mianshi.mycas;

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

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 10:19
 **/
public class AtomicStampedReferenceABA {
    private static AtomicReference<Integer> ar = new AtomicReference<>(0);
    private static AtomicStampedReference<Integer> asr =
            new AtomicStampedReference<>(0, 1);

    public static void main(String[] args) {
        System.out.println("=============演示ABA问题(AtomicReference)===========");
        new Thread(() -> {
            ar.compareAndSet(0, 1);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            ar.compareAndSet(1, 0);
            System.out.println(Thread.currentThread().getName() + "举行了一次ABA操作");
        }, "子线程").start();

        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        boolean res = ar.compareAndSet(0, 100);
        if (res) {
            System.out.println("main乐成修改, 未察觉到子线程举行了ABA操作");
        }

        System.out.println("=============解决ABA问题(AtomicStampReference)===========");
        new Thread(() -> {
            int curStamp = asr.getStamp();
            System.out.println("t1获取当前stamp: " + curStamp);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            asr.compareAndSet(0, 1, curStamp, curStamp + 1);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            asr.compareAndSet(1, 0, asr.getStamp(), asr.getStamp() + 1);
        }, "t1").start();

        new Thread(() -> {
            int curStamp = asr.getStamp();
            System.out.println("t2获取当前stamp: " + curStamp);
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = asr.compareAndSet(0, 100, curStamp, curStamp + 1);
            if (!result) {
                System.out.println("修改失败! 预期stamp: " + curStamp + ", 现实stamp: " + asr.getStamp());
            }
        }, "t2").start();
    }
}

运行效果:

CAS底层原理与ABA问题

AtomicMarkableReference:若是不体贴引用变量中途被修改了多少次,而只体贴是否被修悔改,可以使用AtomicMarkableReference:

package com.raicho.mianshi.mycas;

import java.util.concurrent.atomic.AtomicMarkableReference;

/**
 * @author: Raicho
 * @Description:
 * @program: mianshi
 * @create: 2020-07-17 10:46
 **/
public class AtomicMarkableReferenceABA {
    private static AtomicMarkableReference<Integer> amr = new AtomicMarkableReference<>(0, false);

    public static void main(String[] args) {
        new Thread(() -> {
            amr.compareAndSet(0, 1, false, true);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            amr.compareAndSet(1, 0, true, true);
            System.out.println("子线程举行了ABA修改!");
        }, "子线程").start();

        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        boolean res = amr.compareAndSet(0, 100, false, true);
        if (!res) {
            System.out.println("修改失败! 当前isMarked: " + amr.isMarked());
        }
    }
}

运行效果:

CAS底层原理与ABA问题

参考

知乎:https://zhuanlan.zhihu.com/p/93418208

csdn:https://blog.csdn.net/justry_deng/article/details/83449038

原创文章,作者:28x29新闻网,如若转载,请注明出处:https://www.28x29.com/archives/23024.html