关于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

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

相关推荐
Aileen_0v05 分钟前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
桂月二二2 小时前
Java与容器化:如何使用Docker和Kubernetes优化Java应用的部署
java·docker·kubernetes
liuxin334455662 小时前
学籍管理系统:实现教育管理现代化
java·开发语言·前端·数据库·安全
小马爱打代码2 小时前
设计模式详解(建造者模式)
java·设计模式·建造者模式
栗子~~3 小时前
idea 8年使用整理
java·ide·intellij-idea
2301_801483693 小时前
Maven核心概念
java·maven
Q_19284999063 小时前
基于Spring Boot的电影售票系统
java·spring boot·后端
我要学编程(ಥ_ಥ)4 小时前
初始JavaEE篇 —— 网络原理---传输层协议:深入理解UDP/TCP
java·网络·tcp/ip·udp·java-ee
就爱学编程4 小时前
重生之我在异世界学编程之C语言:数据在内存中的存储篇(下)
java·服务器·c语言
yuanbenshidiaos5 小时前
C++--------------树
java·数据库·c++