乐观锁实现之CAS算法分析

在介绍 CAS 之前,先来了解下什么是乐观锁。

乐观锁(Optimistic Lock)是指对于数据冲突保持一种乐观态度,操作数据时不会对数据加锁,只有到数据提交的时候才通过某种机制来验证数据是否存在冲突。

可以通过使用版本号CAS 算法进行实现,本篇博客主要介绍 CAS 算法的概念,以及对 CAS 算法的实现原理进行分析。

什么是 CAS 算法

CAS:Compare and Swap,即比较再交换,其算法公式如下:

函数公式:CAS(V,E,N)

CAS 操作需要我们提供一个期望值,当期望值与当前线程的变量值相同时,说明还没有线程修改该值,当前线程就可以进行修改,也就是执行 CAS 操作。

但如果期望值与当前线程的变量值不符,则说明该值已被其他线程修改,此时不执行更新操作,但可以选择重新读取该变量并尝试再次修改,也可以放弃操作。

CAS 的实现原理

对 java.util.concurrent.atomic 包下的原子类 AtomicInteger 中的 compareAndSet 方法进行分析,可以发现最终调用的是 sum.misc.Unsafe 这个类。

    /**
     * Atomically sets the value to the given updated value
     * if the current value {@code ==} the expected value.
     *
     * @param expect the expected value
     * @param update the new value
     * @return {@code true} if successful. False return indicates that
     * the actual value was not equal to the expected value.
     */
    public final boolean compareAndSet(long expect, long update) {
        return unsafe.compareAndSwapLong(this, valueOffset, expect, update);
    }

Unsafe 类本身是不安全的,它为了速度,在 Java 的安全标准上做出了一定的妥协。Unsafe 的 CAS 依赖的是 JVM 针对不同的操作系统实现的 Atomic::cmpxchg 函数。

Atomic::cmpxchg 函数使用了汇编的 CAS 操作,并使用 CPU 硬件提供的 lock 信号保证其原子性的实现。

CAS 的优缺点

优点

CAS 是非阻塞的轻量级乐观锁,通过 CPU 指令实现。在资源竞争不激烈的情况下,synchronized 重量锁会进行比较复杂的加锁、解锁和唤醒操作,而 CAS 不会加锁,性能高。

缺点

CPU开销大

在并发量比较高的情况下,如果许多线程反复尝试更新某一个变量却又一直更新不成功,会给 CPU带来很大压力。

不能保证代码块的原子性

CAS 机制所保证的只是一个变量的原子性操作,而不能保证整个代码块的原子性。比如需要保证3个变量共同进行原子性的更新,就不得不使用 synchronized 同步锁了。

 除了以上两个缺点,CAS 还可能会出现 ABA 的问题,那么 ABA 问题又是什么呢?

CAS 中的 ABA 问题

如果一开始位置 V 得到的旧值是 A ,当进行赋值操作时再次读取发现仍然是 A ,并不能说明变量没有被其它线程改变过,有可能是其它线程将变量改为了 B,后来又改回了 A。

 

对于 ABA 问题,主要有两种解决方案:

使用版本号

在变量前面追加版本号,每次变量更新的时候把版本号加一,也就是说,之前的 A-B-A 就会变成 1A - 2B - 3A。

使用并发包的原子类

java.util.concurrent.atomic 包下提供了一个可处理 ABA 问题的原子类 AtomicStampedReference。其 compareAndSet 方法首先会检查当前引用是否等于预期引用,且当前标志是否等于预期标志。

如果全部相等,则以原子方式将该引用的该标志的值设置为给定的更新值。

CAS 的使用场景

  • Atomic 开头的实现类

以 Atomic 开头的实现类的底层都使用了 CAS 算法。

  • 自旋锁

自旋锁是指当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程不会阻塞,而是将循环等待,不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环。可以看成是一个不断自动重试的乐观锁,它是 CAS 算法的一种锁机制的实现。

  • 令牌桶限流器

系统以恒定的速度向桶内增加令牌,每次请求前从令牌桶里面获取令牌,只有获取到令牌就才可以进行访问。

当令牌桶内没有令牌的时候,就会拒绝提供服务。其中的限流器就是通过使用 CAS 算法来维护多线程环境下对令牌的增加和分发的。

参考了如下博客,非常感谢:

Java并发编程之CAS算法

java中的cas - 知乎

おすすめ

転載: blog.csdn.net/j1231230/article/details/120477010