• Java中的volatile为什么不能保证原子性


    在之前的文章中java中的volatile关键字的作用_ErwinNakajima的博客-CSDN博客_java中的volatile关键字的作用

    已经介绍过volatile只能保证可见性和有序性(禁止指令重排序优化),既然保证了变量的可见性,有人会有这样的疑问:

    volatile变量对线程立即可见,那对volatile变量的修改都能立刻反应到其他线程。

    换句话说,volatile变量在各个线程中是一致的,所以volatile变量的运算在多线程下是线程安全的,也就是可以保证原子性

    但是这里面忽略了一个问题,默认运算本身是原子操作,但是实际上对volatile i++操作并不是原子操作,从主内存读->加操作->写到主内存,下面我们分析一下这个问题。

    测试案例

    先通过一段Java案例代码对其进行相关的说明:

    1. package jmm;
    2. public class VolatileDemo {
    3. public static volatile int num = 0;
    4. public static void increase(){
    5. num ++;
    6. }
    7. public static void main(String[] args) throws InterruptedException {
    8. //1、创建10个线程
    9. Thread[] threads = new Thread[10];
    10. for (int i = 0; i < 10; i++) {
    11. //十个线程都调用普通方法
    12. threads[i] = new Thread(()->{
    13. for (int j = 0; j < 1000; j++) {
    14. //num ++ 操作执行1000
    15. increase();
    16. }
    17. });
    18. threads[i].start();
    19. }
    20. //等待所有线程执行完成 才继续执行下面的代码
    21. for (Thread thread : threads) {
    22. thread.join();
    23. }
    24. System.out.println(num);
    25. }
    26. }

    该代码执行后得到的结果多种多样,如:

    在这里插入图片描述

    在这里插入图片描述

    为什么会出现这样的结果呢,接下来采取数据处理流程图进行解释说明。 

    原因

    在Java中,针对多线程共同处理数据操作,通常以如下方式进行:(假设2个cpu)

    在这里插入图片描述

    大家都知道num ++ 不具备原子性

    在CPU对其进行数据处理时,分为两部操作。
    读:num = 0;
    写:num = num + 1;

     在博客缓存一致性协议(MESI)中,说到数据处理操作有如下几个步骤:
    1、CPU1获取主存中,num变量信息时,将其从中拷贝副本高速缓存中,并将其MESI状态标记为E(独享)

    在这里插入图片描述

    2、如果此时CPU2也读取了数据,由于CPU1对其他CPU具有总线嗅探机制,当监听到被监听的数据经过BUS总线,则会将数据的状态信息变更为S(共享)状态。

    在这里插入图片描述

    3、由不同CPU去对自身独有缓存进行加锁操作,BUS总线中的总线裁决判断哪个CPU加锁有效。

    上面是数据操作的大致流程,但想过一个问题没有:

    当CPU对缓存行加锁成功时,使其他CPU对该数据状态进行失效处理。

     但是,如果此时数据,已经在寄存器中经过处理,只是还并未 assign(赋值) 到指定工作内存中呢?如下所示:

    在这里插入图片描述

    此时CPU1工作内存(高速缓存)中将数据进行失效处理

    但如果num = num + 1这个命令还在寄存器中处理,并未assign(赋值)工作内存中,就会出现下面的情况:

     在这里插入图片描述

    CPU1中,针对num ++却在寄存器中正在被操作。
    CPU2获取到缓存行加锁操作,将CPU1中的缓存数据进行失效处理,但并不能寄存器中的数据也进行失效操作。

    导致CPU1中,寄存器将数据num = num + 1进行运算操作,再执行assign(赋值)操作,写回工作内存中。
    此时CPU1 工作内存对应数据并不存在,寄存器会将该信息进行写入工作内存并将其写回主存

    此时会导致CPU1CPU2两个线程操作,都执行了一次num = 1write(写入)操作,也就是两个线程都进行了操作,但结果却是1!!!

    最后总结

    volatile能够保证数据变化,其他线程及时可见性

    但不能保证数据原子性操作。

  • 相关阅读:
    基于Micropython的桌面信息小摆件
    MMDetection训练自己的数据集
    Kube-OVN-安装配置参数选项
    Linux的打包和压缩
    文心一言 VS 讯飞星火 VS chatgpt (108)-- 算法导论10.1 6题
    【双指针】有效三角形的个数
    nohup原理
    SkyWalking告警通知
    Stream强化
    【docker】运行redis
  • 原文地址:https://blog.csdn.net/NakajimaFN/article/details/126518806