关于CAS算法的再一次记录和理解

前言:小弟学习一些东西的时候,遗忘曲线在我身上体现的十分的淋漓尽致。这次回过头看CAS算法的时候,网上的文章有很多,但是我却偶尔回忆不起来作者表述的东西是什么含义,今天我再一次梳理了一下CAS算法的底层原理,再自己帮助自己记录的时候,方便自己回想起来,另外要是能帮到各位同学的话,那就再好不过了。

什么是CAS算法?

如果一个线程失败或挂起不会导致其他线程也失败或挂起,那么这种算法就被称为非阻塞算法 ,也叫无锁算法。而CAS算法就是对非阻塞算法的一种实现。

什么是非阻塞算法?

要说明什么是非阻塞算法,我们就要知道什么是阻塞算法。我们通常在Java代码里面使用的synchronized 关键字实现的算法,就是一种阻塞式。不过可能有同学会说,在java1.6之后进行了改进,有偏向锁的状态,这个时候偏向锁实际上式一种无锁状态,它仅仅是记录了当前对象的所处的线程ID。不过我们这里讲的是整个synchronized的实现,当出现线程竞争的时候,通过锁的升级,就会演变成轻量级锁和重量级锁,这个时候就是阻塞方式的。说了这么多,还没说清楚,什么是阻塞式的?所谓阻塞式,就是当某个资源被某一个线程占有的时候,另一个线程想要访问这个资源的时候,必须等当前占有资源的这个线程释放对资源的占有的时候,这个时候新的线程才能获取到该资源的使用权。如下:

那么CAS算法是如何实现非阻塞式的?

CAS算法通过比较旧的预期值P,内存值V,和新的待写入的值R的比较,来实现非阻塞式。这里我需要先说明一下内存当中的以上三个值的模型,这样大家好明白一点。如下图:

每一个线程当获取到该内存当中的值的时候,都会在自己的线程栈中保留一个副本。因此,当线程A和线程B同时去读取内存当中的值Value的时候,都会在自己的线程保留对象的副本,Value_A和Value_B,只有先清楚这个,才能说明白CAS算法里面的预期值,内存值和新的待写入的值的含义。 还是以上面这个图为例,假设,内存当中的value初始值为1,那么一开始线程A和线程B保存的副本都是1,这个时候三者保存的值都是一样的。但是这个时候,线程A修改了自己线程的Value_A的值为2,为了保证所有的值都是一样的,需要以下几个步骤。

  1. 修改自己的Value_A = 2
  2. 将Value_A的值同步到内存Value中,使Value=2
  3. 线程B主动再读取一次内存的值或者内存主动通知线程B,使Value_B =2。

只有当上面这个几个步骤都生效的时候,才能保证三者的值都是实时同步的。

明白了上面的这个步骤,我们在看什么是预期值P,内存值V,和新的待写入的值R。 预期值P:就是线程里面保存的副本,Value_A和Value_B 内存值V:就是内存当中的Value 新的待写入的值R:需要更新的新值,这里我们以线程A举例子,就是New_Value_A。

Compare-And-Swap

CAS算法的核心就是Compare-And-Swap。先比较,再交换。什么意思,假设我New_Value_A = 2,这个时候,我想要更新内存当中的值,我就会先比较预期值P(Value_A)和内存值Value(V)是否相等,如果相等,我们就认为这个时候,我们本地的值是最新的,没有其他线程进行更改,我们可以进行更新(这里其实并不完全正确,有一个ABA的问题,我们后面再说)。否则,我们认为,当前线程里的值并不是最新的,不做任何操作。

ABA问题

其实上面的CAS算法有一个问题,就是ABA。什么是ABA问题?以上述的模型作例子,我们都知道,我们只有在满足P和V值相同的时候,我们才会将线程A里面的N值更新到内存当中,但是如果线程B在线程A更新N值之前,他已经更新了2次内存的值,先将Value设置成2,再将Value设置1,这个时候,对于线程A,他是不可预知的,他仍然认为本地的值是最新的,然而内存当中的值已经被更改过2次了。

实际使用

其实在日常的使用当中,我们并不会直接使用cas方法,因为它是通过sun.misc.Unsaf去实现的。

java 复制代码
    // CAS 操作的核心对象
    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();

    public final int getAndIncrement() {
        return U.getAndAddInt(this, VALUE, 1);
    }

    /**
     * Atomically decrements by one the current value.
     *
     * @return the previous value
     */
    public final int getAndDecrement() {
        return U.getAndAddInt(this, VALUE, -1);
    }

    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the previous value
     */
    public final int getAndAdd(int delta) {
        return U.getAndAddInt(this, VALUE, delta);
    }

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return U.getAndAddInt(this, VALUE, 1) + 1;
    }

    /**
     * Atomically decrements by one the current value.
     *
     * @return the updated value
     */
    public final int decrementAndGet() {
        return U.getAndAddInt(this, VALUE, -1) - 1;
    }

    /**
     * Atomically adds the given value to the current value.
     *
     * @param delta the value to add
     * @return the updated value
     */
    public final int addAndGet(int delta) {
        return U.getAndAddInt(this, VALUE, delta) + delta;
    }

但是java提供了我们很多可供间接使用的类

复制代码
java.util.concurrent.atomic

这里具体的使用方式,就不再细说了,各位同学可以自己试着操作。

相关推荐
坐吃山猪2 小时前
SpringBoot01-配置文件
java·开发语言
我叫汪枫2 小时前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao3 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
Swift社区4 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT5 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy5 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss6 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续7 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0447 小时前
ReAct模式解读
java·ai