java之CAS

1、CAS含义

cas顾名思义是比较交换,实现了并发安全特性的原子性,是基于硬件平台的汇编指令,也就是说基于硬件实现的,通过比较预期的值是否和内存中值是否一致,如果一致则更新,如不一致则重新获取内存的值进行比较。和其他实现原子性的方式不一样的是此方式不需要加锁,大大提高了执行效率,解决了加锁释放锁导致的上下文切换的问题。

2、AtomicInteger

以AtomicInteger为例,此原子类是并发安全的,内部是通过CAS和voliate实现的。如何证明此AtomicInteger是并发安全的呢,举个例子如下:利用100个线程对i进行加1操作,无论允许多少次最后得到结果就是100。为什么呢?内部怎么实现的?

    private static AtomicInteger i = new AtomicInteger(0);
    public static void main(String[] args) {
        for (int i=0;i<100;i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    add();
                }
            }).start();
        }
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(i.get());
    }
    public static void add() {
        i.addAndGet(1);
    }

我们看一下内部源码:

public final int addAndGet(int delta) {
        return unsafe.getAndAddInt(this, valueOffset, delta) + delta;
}

使用unsafe类,unsafe类是java中的原子类, unsafe类中基本都是native方法,native方法底层通过JNI调用c的方法库了,至此java是看不到的。而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;
    }
public native int getIntVolatile(Object var1, long var2);

public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);

以上图中,各个参数的含义:

var1: 是AtomicInteger对象

var2:下图AtomicInteger源码中valueoffset,表示value的引用地址

var5:getIntVolatile(var1,var2)  通过此方法获取的值,我们从上一讲java之voliate中知道,每个线程对于共享对象的修改操作,并非直接修改内存,而是将主内存的数据,读取到自己的工作内存中,而var5则是工作内存的value的值

var4:表示需要增加的值

var5+var4:更新的值

    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;

基本原理:

var5为工作内存的值,调用compareAndSwapInt方法后,底层通过比较var1对象中的value值和var5进行比较,为什么是var1对象中的value值?  我们可以看到上图中的value声明成为了voliate,我们知道,当设置voliate之后,value是具有可见性的,当主内存修改后,每个线程的value则会失效,并且重新读取主内存的值,所以此value也可以说明是主内存的值。比较之后,如果一致,说明没有被修改,则将值更新为var5+var4,如果不一致,则进入下个循环重新读取工作内存的值,直到数据一致。

举个并发例子说明其原理:

线程A和线程B同时对上图中的i进行add操作:

(1)当线程A执行到getIntVolatile后,var5拿到的工作内存中的值为0,线程A被挂起

(2)此时线程B执行到getIntVolatile,并且var5也为0,然后执行compareAndSwapInt操作,比较主内存和工作内存的值为0,数据一致,则value值被更新为1。

(3)线程A拿到CPU执行权,然后执行compareAndSwapInt操作,判断var5和value不一致,说明此value被其他线程修改了,进入下一个循环。直到成功

CAS内部使用自旋的方式进行CAS更新(while循环进行CAS更新,如果更新失败,则循环再次重试)

3、CAS缺点

(1)由于是自旋操作,假如工作线程的值和主线程值一直不相等,则陷入死循环中,增大了CPU的开销

(2)ABA的问题,假如线程A,对value进行了+1,然后又对value进行-1的操作,实际上value值是没有变化的。怎么解决此问题?可以添加版本号,当value+1后,版本号也随之+1,然后-1后,版本号也随之+1,compareAndSet判断时不仅仅需要判断值是否相等还需要判断版本号是否相等。JDK的Atomic包里提供了一个类AtomicStampedReference也是此原理。

(3)CAS仅仅只能保证一个共享变量的原子性,不能保证多个变量的原子性。相应实现多个变量的原子性,可以将多个变量封装到一个对象中,然后使用AtomicStampedReference来操作此对象的更新。

你可能感兴趣的:(java,java,开发语言)