Java中的CAS

​ Java中的CAS

CAS全称 Compare And Swap(比较与交换),是一种无锁算法。在不使用锁(没有线程被阻塞)的情况下实现多线程之间的变量同步,java.util.concurrent包中的原子类就是通过CAS来实现了乐观锁。

基本介绍

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。

使用场景

有的时候我们需要对变量进行操作,如果是多线程,则有可能达不到我们的预期结果,Synchronized等关键字,可以解决问题,但是Synchronized关键字会让没有得到锁资源的线程进入BLOCKED状态,而后在争夺到锁资源后恢复为RUNNABLE状态,这个过程中涉及到操作系统用户模式内核模式的转换,代价比较高。这个时候我们就可以使用Atomic的一些原子操作来进行。

public class CASTest {

    public static volatile int count = 0;

    public static AtomicInteger atomicCount = new AtomicInteger();

    public static void main(String[] args) throws InterruptedException {

        for (int i = 0; i < 2; i++) {
            new Thread(() -> {
                for (int j = 0; j < 10000; j++) {
                    count++;
                    atomicCount.incrementAndGet();
                }
                System.out.println("count++ end!");
            }).start();
        }

        Thread.sleep(2000);
        System.out.println(count);
        System.out.println(atomicCount.get());
    }
}

来看一下这个的代码输出

count++ end!
count++ end!
18003
20000

可以看到volatile也没有办法保证运算的原子性。AtomicInteger使用CAS操作,可以保证运算的原子性。

AtomicInteger的实现原理

我们可以看一下AtomicInteger中incrementAndGet()的源码

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

其是通过Unsafe来实现的,我们进入Unsafe中的getAndInt()方法中

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;
}

compareAndSwapInt是本地的方法,是通过CPU的cmpxchg指令,去比较寄存器中的 A 和 内存中的值 V。如果相等,就把要写入的新值 B 存入内存中。如果不相等,就将内存值 V 赋值给寄存器中的值 A。然后通过Java代码中的while循环再次调用cmpxchg指令进行重试,直到设置成功为止。

CAS存在的问题

  1. ABA问题CAS需要在操作值的时候检查内存值是否发生变化,没有发生变化才会更新内存值。但是如果内存值原来是A,后来变成了B,然后又变成了A,那么CAS进行检查时会发现值没有发生变化,但是实际上是有变化的。ABA问题的解决思路就是在变量前面添加版本号,每次变量更新的时候都把版本号加一,这样变化过程就从“A-B-A”变成了“1A-2B-3A”。JDK从1.5开始提供了AtomicStampedReference类来解决ABA问题。

  2. 并发高循环时间长的时候开销大CAS操作如果长时间不成功,会导致其一直自旋,给CPU带来非常大的开销。

  3. 只能保证一个共享变量的原子操作对一个共享变量执行操作时,CAS能够保证原子操作,但是对多个共享变量操作时,CAS是无法保证操作的原子性的。

    Java从1.5开始JDK提供了AtomicReference类来保证引用对象之间的原子性,可以把多个变量放在一个对象里来进行CAS操作。

参考

https://tech.meituan.com/2018/11/15/java-lock.html

关注公众号:蜜蜂技术巢了解更多知识

你可能感兴趣的:(Java中的CAS)