美文网首页
Java中的CAS

Java中的CAS

作者: 久梦歌行 | 来源:发表于2019-10-02 23:17 被阅读0次

    ​ 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

          本文链接:https://www.haomeiwen.com/subject/rknrpctx.html