高级并发编程系列十二(一文搞懂cas)

194 阅读4分钟

1.引子

今天这篇文章,我们暂时结束了锁小节内容,进入原子性小节,内容稍微有点偏底层,不过你不用担心,相对还是比较好理解的。

我们一直在说线程安全,那么线程安全有三个基本要素,你知道是哪三个吗?它们分别是:

  • 可见性
  • 原子性
  • 有序性

可见性是指内存可见性,java内存模型你应该要熟系,如果你不熟悉的话,先看一下下面这个图:

image.png

如图所示,在java内存模型中,每个线程首先都有自己的私人空间,称为:工作内存。所有线程共享公共空间,称为:主内存

那么具体可见性,是指什么呢?指的是当一个线程修改某个变量值后,如果要保障可见性(其它线程可以看见的话),需要将变量值同步到主内存中。同理一个线程操作某个变量,如果要保障可见性的话,需要首先从主内存同步变量到自己的工作内存中。这就是线程安全第一要素:可见性。

我们接着看线程安全第二要素:原子性。所谓原子性,即是一个整体不可分割,我们前面掌握的锁,即是在应用层保障原子性的手段。今天我们要学习的是偏底层的原子性,是由cpu指令层面的原子性。

最后再来看线程安全的第三要素:有序性。这里的有序性比较好理解,指的是线程内有序,线程之间还是无序的。

好了,解释清楚线程安全三要素:可见性、原子性、有序性。我们可以介绍cas了,那么cas到底是什么呢?cas你可以理解为是一种思想,它是英文compare and swap的缩写,即比较和交换,是由cpu在指令层面保障原子性,提供线程安全的一种解决方案。我们熟系的乐观锁,以及juc包中相关原子类,比如AtomicInteger的底层实现。

关于cas,它具体的实现原理是这样的,它有三个值:内存值A、期望值B、更新值C。每个线程执行更新的时候

  • 它会首先比较内存值A,是否等于期望B
  • 如果等于,则将内存值A更新成C,此次cas操作成功
  • 如果不等于,则说明已经被其它线程更新过了,那么此次cas操作失败
  • 循环进入下次cas操作,直到操作成功为止

文字描述稍微有点抽象,我们看一个图,你就能理解了,看图:

image.png

理解了cas的本质以后,我们最后通过一个案例来演示一下cas的实现原理。

2.案例

2.1.模拟cas实现

案例描述:

  • 假设初始值value等于0,即cas三个值中的内存值
  • 定义doCas方法,方法参数分别是cas三个值中的期望值,与更新值
  • doCas方法逻辑比较简单,将期望值expectedValue,与内存值value进行比较
  • 如果相等,cas操作成功,将内存值更新为updateValue
  • 如果不相等,cas操作失败,内存值此次不做更新
  • 在main方法中,创建两个线程:thread_1、thread_2
  • thread_1执行cas操作:doCas(0, 1),将内存值更新为1
  • thread_2执行cas操作:doCas(0, 2),将内存值更新为2
  • 预期执行结果:两个线程,有且仅有一个线程更新成功
package com.anan.edu.common.newthread.atomic;
​
/**
 * 模拟cas实现原理
 *
 * @author ThinkPad
 * @version 1.0
 * @date 2020/11/15 17:07
 */
public class CasDemo {
​
    /**
     * 初始值为0
     */
    private static int value = 0;
​
    /**
     * 模拟cas操作:synchronized锁,模拟了底层原子性操作
     * @param expectedValue  期望值
     * @param updateValue   更新值
     * @return  返回内存值
     */
    public static synchronized int doCas(int expectedValue, int updateValue){
        int oldValue = value;
        // 比较内存值,是否等于期望值,如果相等,则更新成功
        if(expectedValue == oldValue){
            value = updateValue;
            System.out.println("线程【" + Thread.currentThread().getName() + "】更新值成功.将值更新为:" + updateValue);
        }else{
            System.out.println("线程【" + Thread.currentThread().getName() + "】更新值失败.");
        }
        // 返回内存值
        return oldValue;
    }
​
    /**
     * main方法
     * @param args
     */
    public static void main(String[] args) throws  Exception{
        // 1.创建2个线程,并行更新value值
        // 线程1,期望将值更新成1
        Thread thread_1 = new Thread(() -> { doCas(0, 1); }, "线程_1");
        thread_1.start();
​
        // 线程2,期望将值更新成2
        Thread thread_2 = new Thread(() -> {doCas(0,2);},"线程_2");
        thread_2.start();
​
        // 2.当两个线程执行完成,打印更新成功后的值
        thread_1.join();
        thread_2.join();
        System.out.println("value最后更新的值是:" + value);
​
    }
​
}

2.2.执行结果及分析

D:\02teach\01soft\jdk8\bin\java  com.anan.edu.common.newthread.atomic.CasDemo
线程【线程_1】更新值成功.将值更新为:1
线程【线程_2】更新值失败.
value最后更新的值是:1
​
Process finished with exit code 0