编程语言
首页 > 编程语言> > 程序员深夜惨遭老婆鄙视,原因竟是CAS原理太简单?| 每一张图都力求精美

程序员深夜惨遭老婆鄙视,原因竟是CAS原理太简单?| 每一张图都力求精美

作者:互联网

夜黑风高的晚上,一名苦逼程序员正在疯狂敲着键盘,突然他老婆带着一副睡眼朦胧的眼神瞟了下电脑桌面。于是有了如下对话:

mark

悟空
种树比较好的时间是十年前,其次是现在。
自主开发了Java学习平台、PMP刷题小程序。目前主修Java多线程SpringBootSpringCloudk8s
本公众号不限于分享技术,也会分享工具的使用、人生感悟、读书总结。

夜黑风高的晚上,一名苦逼程序员正在疯狂敲着键盘,突然他老婆带着一副睡眼朦胧的眼神瞟了下电脑桌面。于是有了如下对话:

老婆:这画的图是啥意思,怎么还有三角形,四边形?

我:我在画CAS的原理,要不我跟你讲一遍?

老婆:好呀!

请开始你的表演

案例:甲看见一个三角形积木,觉得不好看,想替换成五边形,但是乙想把积木替换成四边形。(前提条件,只能被替换一次)

案例

甲比较鸡贼,想到了一个办法:“我把积木带到另外一个房间里面去替换,并上锁,就不会被别人打扰了。”(这里用到了排他锁synchronized

乙觉得甲太不厚道:“房间上了锁,我进不去,我也看不见积木长啥样。(因上了锁,所以不能访问)”

甲把房间锁住了

于是甲、乙想到了另外一个办法:谁先抢到积木,谁先替换,如果积木形状变了,则不允许其他人再次替换。(比较并替换CAS

于是他们就开始抢三角形积木:

乙抢到,替换成三角形,甲替换成五边形,ABA问题

老婆听完后,觉得这三种场景都太简单了,原来计算机这么简单,早知道我也去学计算机。。。

mark

被无情鄙视了,好在老婆居然听懂了,不知道大家听懂没?

回归正传,我们用计算机术语来讲下Java CAS的原理

一、Java CAS简介

CAS的全称:Compare-And-Swap(比较并交换)。比较变量的现在值与之前的值是否一致,若一致则替换,否则不替换。

CAS的作用:原子性更新变量值,保证线程安全。

CAS指令:需要有三个操作数,变量的当前值(V),旧的预期值(A),准备设置的新值(B)。

CAS指令执行条件:当且仅当V=A时,处理器才会设置V=B,否则不执行更新。

CAS的返回指:V的之前值。

CAS处理过程:原子操作,执行期间不会被其他线程中断,线程安全。

CAS并发原语:体现在Java语言中sun.misc.Unsafe类的各个方法。调用UnSafe类中的CAS方法,JVM会帮我们实现出CAS汇编指令,这是一种完全依赖于硬件的功能,通过它实现了原子操作。由于CAS是一种系统原语,原语属于操作系统用于范畴,是由若干条指令组成,用于完成某个功能的一个过程,并且原语的执行必须是连续的,在执行过程中不允许被中断,所以CAS是一条CPU的原子指令,不会造成所谓的数据不一致的问题,所以CAS是线程安全的。

二、能写几行代码说明下吗?

在上篇讲volatile时,讲到了如何使用原子整型类AtomicInteger来解决volatile的非原子性问题,保证多个线程执行num++的操作,最终执行的结果与单线程一致,输出结果为20000。

这次我们还是用AtomicInteger。

首先定义atomicInteger变量的初始值等于10,主内存中的值设置为10

AtomicInteger atomicInteger = new AtomicInteger(10);

然后调用atomicInteger的CAS方法,先比较当前变量atomicInteger的值是否是10,如果是,则将变量的值设置为20

atomicInteger.compareAndSet(10, 20);

设置成功,atomicInteger更新为20

当我们再次调用atomicInteger的CAS方法,先比较当前变量atomicInteger的值是否是10,如果是,则将变量的值设置为30

atomicInteger.compareAndSet(10, 30);

设置失败,因atomicInteger的当前值为20,而比较值是10,所以比较后,不相等,故不能进行更新

完整代码如下:

package com.jackson0714.passjava.threads;
import java.util.concurrent.atomic.AtomicInteger;
/**
 演示CAS compareAndSet 比较并交换
 * @author: 悟空聊架构
 * @create: 2020-08-17
 */
public class CASDemo {
    public static void  main(String[] args) {
        AtomicInteger atomicInteger = new AtomicInteger(10);
        Boolean result1 = atomicInteger.compareAndSet(10,20);
        System.out.printf("当前atomicInteger变量的值:%d 比较结果%s\r\n", atomicInteger.get(), result1);
        Boolean result2 = atomicInteger.compareAndSet(10,30);
        System.out.printf("当前atomicInteger变量的值:%d, 比较结果%s\n" , atomicInteger.get(), result2);
    }
}

执行结果如下:

当前atomicInteger变量的值:20 比较结果true
当前atomicInteger变量的值:20, 比较结果false

atomicInteger比较并交换的示例结果

我们来对比看下原理图理解下上面代码的过程

mark

mark

mark

mark

mark

图画得非常棒!

mark

上述的场景和我们用Git代码管理工具是一样的,如果有人先提交了代码到develop分支,另外一个人想要改这个地方的代码,就得先pull develop分支,以免提交时提示冲突。

三、能讲下CAS底层原理吗?

源码调试

这里我们用atomicInteger的getAndIncrement()方法来讲解,这个方法里面涉及到了比较并替换的原理。

示例如下:

public static void  main(String[] args) throws InterruptedException {
    AtomicInteger atomicInteger = new AtomicInteger(10);
    Thread.sleep(100);

    new Thread(() -> {
        atomicInteger.getAndIncrement();
    }, "aaa").start();

    atomicInteger.getAndIncrement();
}

mark

getAndIncrement方法会调用unsafe的getAndAddInt方法,

public final int getAndIncrement() {
    return unsafe.getAndAddInt(this, valueOffset, 1);
}

1

4

(7)切换到main线程,进行比较并替换

this.compareAndSwapInt(var1, var2, var5, var5 + var4)

var5=10,通过var1和var2获取到的值也是10,因为没有其他线程修改变量。compareAndSwapInt的源码我们后面再说。

所以比较后,发现变量没被其他线程修改,可以进行替换,替换值为var5+var4=11,变量值替换后为 11,也就是自增1。这行代码执行结果返回true(自增成功),退出do while循环。return值为变量更新前的值10。

5

(8)切换到子线程aaa,进行比较并自增

因为此时aaa线程的var5=10,而主内存中的值已经更新为11了,所以比较后发现被其他线程修改了,不能进行替换,返回false,继续执行do while循环。

6

7

8

至此,getAndIncrement方法的整个原子自增的逻辑就debug完了。所以可以得出结论:

先比较线程中的副本是否与主内存相等,相等则可以进行自增,并返回副本的值,若其他线程修改了主内存中的值,当前线程不能进行自增,需要重新获取主内存的值,然后再次判断是否与主内存中的值是否相等,以此往复。

四、CAS有什么问题?

不知道大家发现没,aaa线程可能会出现循环多次的问题,因为其他线程可能将主内存的值又改了,但是aaa线程拿到的还是老的数据,就会出现再循环一次,就会给CPU带来性能开销。这个就是自旋

五、小结

本篇从和老婆的对话开始,以通俗的语言给老婆讲了CAS问题,其中还涉及到了并发锁。然后从底层代码一步一步debug,深入理解了CAS的原理。

每一张图都力求精美!分享+在看啊,大佬们!

彩蛋:还有一个ABA问题没有给大家讲,另外这里怎么不是AAB(拖拉机),AAA(金花)?

4个A

这周前三天写技术文章花了大量时间,少熬夜,睡觉啦 ~ 我们下期再来讲ABA问题,小伙伴们分享转发下好吗?您的支持是我写作最大的动力~

悟空,一只努力变强的码农!我要变身超级赛亚人啦!

悟空

公众号

标签:var5,10,CAS,atomicInteger,鄙视,程序员,线程,内存
来源: https://blog.51cto.com/u_11950846/2716731