Loading

Java的CAS刨析

一、什么是CAS

CAS的全称叫做Compare And Swap,即比较并交换,是一种原子操作,同时CAS是一种乐观机制。在java.util.concurrent包中的很多功能都是建立在CAS之上。如 ReenterLock 内部的 AQS,各种原子类,其底层都用 CAS来实现原子操作。

二、如何解决并发安全问题

在Java并发中,我们最初接触的应该就是synchronized关键字了,但是synchronized属于重量级锁,很多时候会引起性能问题,volatile也是个不错的选择,但是volatile不能保证原子性,只能在某些场合下使用。

像synchronized这种独占锁属于悲观锁,它是在假设一定会发生冲突的,那么加锁恰好有用,除此之外,还有乐观锁,乐观锁的含义就是假设没有发生冲突,那么我正好可以进行某项操作,如果要是发生冲突呢,那我就重试直到成功,乐观锁最常见的就是CAS。

最常见的就是i++的例子,如下面代码所示:

public class Test {

    public volatile int i;

    public synchronized void add() {
        i++;
    }
}

这种方法在性能上可能会差一点,我们还可以使用AtomicInteger,就可以保证i原子的++了。

public class Test {

    public AtomicInteger i;

    public void add() {
        i.getAndIncrement();
    }
}

我们来看一下getAndIncrement()方法的内部是什么。

/**
     * Atomically increments by one the current value.
     *
     * @return the previous value
     */
    public final int getAndIncrement() {
        return unsafe.getAndAddInt(this, valueOffset, 1);
    }

再深入到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;
    }

可以看到,CAS 的思想很简单:三个参数,一个当前内存值 V、旧的预期值 A、即将更新的值 B,当且仅当预期值 A 和内存值 V 相同时,将内存值修改为 B 并返回 true,否则什么都不做,并返回 false。代码中首先调用getIntVolatile取到内存中的值,再用当前值与内存中的值进行比较,如果相等则写入新的值,并返回true循环结束,反之继续取值知道当前值与预期值相等。其中compareAndSwapInt是同通过cmpxchgl CPU指令来完成的是个原子操作。

三、CAS常见问题

1、ABA问题

如线程 1 从内存位置 V 取出 A,这时候线程 2 也从内存位置 V 取出 A,此时线程 1 处于挂起状态,线程 2 将位置 V 的值改成 B,最后再改成 A,这时候线程 1 再执行,发现位置 V 的值没有变化,尽管线程 1 也更改成功了,但是不代表这个过程就是没有问题的。

2、自旋问题

从源码可以知道所说的自选无非就是操作结果失败后继续循环操作,这种操作也称之为自旋锁,是一种乐观锁机制,一般来说都会给一个限定的自选次数,防止进入死循环。自旋锁的优点是不需要休眠当前线程,因为自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是休眠当前线程是提高并发性能的关键点,这是因为减少了很多不必要的线程上下文切换开销。但是,如果 CAS 一直操作不成功,会造成长时间原地自旋,会给 CPU 带来非常大的执行开销。

 

posted @ 2020-11-03 00:04  Charming-Boy  阅读(91)  评论(0编辑  收藏  举报