juc · 2019-09-17 0

Java中的CAS的使用及ABA问题的解决

一、CAS

CAS的全称是Compare-And-Swap,它是一条CPU并发原语

它的功能是判断内存某个位置的值是否为预期值,如果是则更改为新的值,这个过程是原子的

CAS并发原语体现在Java语言中就是sun.misc.Unsafe类中各个方法,调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令,这个一种完全依赖于硬件的功能,通过它实现了原子操作。CAS是一条CPU原子指令,不会造成所谓的数据不一致问题。

二、Unsafe类

Unsafe类是CAS的核心类,由于Java方法无法直接访问底层系统,需要通过本地(native)方法来访问,Unsafe相当于一个后门,基于该类可以直接操作特定内存的数据,Unsafe类其内部方法操作可以像C的指针一样直接操作内存

Unsafe实现CAS的方法,比较当前工作内存中的值和主内存中的值,如果相同则执行规定的操作,返回true,否则返回false

Unsafe

三、CAS缺点

1.只能保证一个共享变量的原子操作

当对一个共享变量执行操作时,我们可以使用循环CAS的方式来保证原子操作,但是,对多个共享变量操作时,循环CAS就无法保证操作的原子性,这个时候可以用锁来保证原子性

2.CAS会导致ABA问题

CAS算法实现一个重要前提需要取出内存中某时刻的数据并在当下时刻比较并替换,那么在这个时间差会导致数据的变化

比如将一个线程A从内存位置v中读取X;这时候另一个线程B也从内存中取出X,并且写进行了一些操作将值变为Y,然后线程B又v位置的数据变成X;这时候线程A进行CAS操作发现内存中仍然是A,然后线程A操作成功,尽管线程A的CAS操作成功,但是不代表这个过程没有问题

四、AtomicInteger类对CAS的使用

代码:

使用AtomicInteger实现原子操作

public class VolatileTest {

    public static void main(String[] args) throws InterruptedException {
        ShareData shareData = new ShareData();
        for (int i = 0; i < 20; i++){
            new Thread(()->{
                for (int j = 0; j < 1000; j++){
                    shareData.numberVolatile++;
                    shareData.atomicInteger.getAndIncrement();
                }
            }, String.valueOf(i)).start();
        }

        while (Thread.activeCount() > 2){
            Thread.yield();
        }

        System.out.println("numberVolatile:"+shareData.numberVolatile);
        System.out.println("atomicInteger:"+shareData.atomicInteger);
    }

}

class ShareData {

    public volatile int numberVolatile = 0;

    public AtomicInteger atomicInteger = new AtomicInteger(0);

}

结果:

volatile

分析:

AtomicInteger的value变量使用volatile保证线程间的可见性

AtomicInteger

AtomicInteger对象调用Unsafe方法的getAndAddInt方法,getAndAddInt方法循环调用compareAndSwapInt方法

compareAndSwapInt判断内存某个位置的值是否为预期值,如果是则更改为新的值,返回true,否则返回false

getAndAddInt

五、AtomicStampedReference解决ABA问题

AtomicStampedReference为时间戳原子引用

public class ABADemo {

    private static AtomicReference<Integer> atomicReference = new AtomicReference<>(100);
    private 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 {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(atomicReference.compareAndSet(100, 2019));
            System.out.println(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 第1次版本号:"+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 第3次版本号:"+atomicStampedReference.getStamp());
        }, "t3").start();

        new Thread(()->{
            int stamp = atomicStampedReference.getStamp();
            System.out.println(Thread.currentThread().getName() + "t 第1次版本号:"+stamp);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            boolean result = atomicStampedReference.compareAndSet(100, 2019, stamp, stamp + 1);
            System.out.println(Thread.currentThread().getName() + "t result:"+result);
            System.out.println(Thread.currentThread().getName() + "t 当前实际最新值:"+atomicStampedReference.getReference());
        }, "t4").start();

    }
}

结果:

AtomicStampedReference