多线程(进阶二:CAS)

目录

一、CAS的简单介绍

CAS逻辑(用伪代码来描述)

二、CAS在多线程中简单的使用

三、原子类自增的代码分析

都看到这了,点个赞再走吧,谢谢谢谢谢


一、CAS的简单介绍

CAS的全称:“Compare And Swap”,字面意思是 “比较和交换”;一个CAS涉及到以下操作,有两个寄存器:A,SwapB,还有内存的值:AddressC。

先判断寄存器A是否和AddressC的值相同,相同,SwapB的值和AddressC的值进行交换,返回成功操作,否则返回失败操作。这里的交换值我们也可以理解成赋值操作,因为寄存器中的值我们不关心,用完就丢掉了,只关心内存中的值。

CAS逻辑(用伪代码来描述)

伪代码:

这里有两个寄存器:expectValue,swapValue,还有内存的值:address,初始化如图:

进入if语句,判断内存的值和寄存器e的值是否相等,如果相等,就交换寄存器swap和内存address的值,如图:

如图:

然后返回true,如果if条件不成立则返回false。

在计算机中,上述操作在计算机只是一条指令,因为单个指令的原因,所以CAS指令是原子的

CAS指令不涉及到加锁,阻塞。基于CAS指令,合理使用的话,在多线程中,我们可以实现无锁编程;因为之前我们讨论并发编程的线程安全问题时,是通过加锁,阻塞这样方式解决线程安全问题,因为会有阻塞,所以性能也就会降低,用CAS指令实现无锁编程,也能保证线程安全,不涉及到阻塞,这样性能就能得到很大的提升,在多线程编程中打开了新世界的大门。


二、CAS在多线程中简单的使用

因为CAS是CPU的指令,有的cpu可能不支持CAS,但主流的CPU(x86,arm...)都是支持的。

CAS本身是CPU的指令,操作系统对其做了封装,jvm又对操作系统提供的api又做了一层封装。java中CAS的api是放在unsafe中的,这个包名的意思,顾名思义也是“不安全”的意思,一般在java中不建议使用,java的标准库中,对于CAS进行了进一步的封装,把CAS的一些操作封装成工具类,供程序猿使用。而主要的一个工具,叫做 “原子类”。

java.util.concurrent.atomic  包下,里面的类就是基于上述方式实现的,典型的类就是AtomicInteger类,里面有很多方法可以实现数值的自增、自减,以及基本的加减操作。下面的代码案例也是使用AtomicInteger展示。

我们以前写过一个代码,让两个线程实现一个变量自增10_0000次,如下代码是线程不安全。

public class AtomicIntegerTest1 {
    public static int count = 0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count++;
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行结果:

输出结果也和我们预期结果不同,肯定是线程不安全的,而原因就是因为count++操作不是原子的,在计算机中有三个指令

这里,我们使用CAS的方式,来实现让两个线程实现一个变量自增10_0000次,代码如下

public class AtomicIntegerTest1 {
    public static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

执行结果:

因为这里用CAS的方式,原本count++操作在计算机中有3条指令,不是原子的,肯定线程不安全;但是用CAS的方式就可以把像count++这样的操作,用一条指令完成,是原子的,在这里就不涉及到线程安全问题了。

AtomicInteger中有很多方法:自增,自减,+=,-=等待,这里就不展开讨论了。


三、原子类自增的代码分析

代码还是上述的代码,如下:

public class AtomicIntegerTest1 {
    public static AtomicInteger count = new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 50000; i++) {
                count.getAndIncrement();//和count++意思一样
            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

在标准库中,getAndIncrement方法是这样的:

这里比较难理解,我们用伪代码的形式介绍,伪代码如下:

当有两个线程对同一个变量进行自增操作时,执行流程是这样的:

这里的oldValue是寄存器中的值,它的值就是AtomicInteger括号里面初始化的值,value是内存中的值。从上到下,把内存value的值都赋值给oldValue,两个线程拿到的都是同一个内存value的值。

这时,右边的线程while循环里的判断,先执行CAS这里的操作,如图:

        

执行完CAS操作后返回true,然后while循环里true != ture,条件不成立,不执行while循环内的代码。

当左边线程进入while循环里面的判断语句时,如图:

也会进入CAS操作,这里因为内存value的值修改了,当前线程寄存器oldValue值还是0,给oldValue+1,会返回false,这时循环条件false != true成立,就会执行oldValue = value操作,如图:

再次进入循环条件里面执行CAS操作,这时候value == oldValue,所以会让oldvalue+1赋值给value,这时候value的值就是2了,如图:

所以,两个线程不管顺序是啥样的,使用getAndIncrement方法都不会出现线程安全问题,因为CAS操作本身就是原子的,原因的逻辑理解也大概是下面这样的:

如果cas不成功,会重复上面的操作,再次读取数据,这次读取到的数据就是正确的了,cas也就能成功。意思就是这个方法里面会判断拿到的值是不是最新值,如果不是就去拿最新的值,再去CAS,这时候因为拿到的是最新值,所以这时能CAS成功。


都看到这了,点个赞再走吧,谢谢谢谢谢

相关推荐

  1. [Python] 进程与线

    2023-12-13 22:20:01       28 阅读

最近更新

  1. TCP协议是安全的吗?

    2023-12-13 22:20:01       16 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2023-12-13 22:20:01       16 阅读
  3. 【Python教程】压缩PDF文件大小

    2023-12-13 22:20:01       15 阅读
  4. 通过文章id递归查询所有评论(xml)

    2023-12-13 22:20:01       18 阅读

热门阅读

  1. vue3父子传值实现弹框功能

    2023-12-13 22:20:01       36 阅读
  2. CentOS 7 部署pure-ftp

    2023-12-13 22:20:01       44 阅读
  3. LeetCode //C - 1. Two Sum

    2023-12-13 22:20:01       36 阅读
  4. 【编码器】位置管理器BoosterPack

    2023-12-13 22:20:01       37 阅读
  5. 重载和重写的区别

    2023-12-13 22:20:01       39 阅读
  6. ARM day8

    ARM day8

    2023-12-13 22:20:01      34 阅读
  7. lzma --- 用 LZMA 算法压缩

    2023-12-13 22:20:01       33 阅读
  8. SpringBoot登录校验-JWT令牌

    2023-12-13 22:20:01       38 阅读
  9. MySQL_9.B-数索引

    2023-12-13 22:20:01       31 阅读