volatile不具有原子性的理解

volatile不具有原子性的理解

以下为概念理解:

1.1 缓存一致性问题
在这里插入图片描述
1、现代计算机系统在存储设备与处理器之间加了一层读写速度尽可能解决处理器运算速度的高速缓存来作为内存与处理器之间的缓冲: 将运算需要使用到的数据复制到缓存中, 让运算能快速进行, 当运算结束后再从缓存同步回内存之中, 这样处理器就无须等待缓慢的内存读写.
2、缓存一致性问题:在多处理器系统中, 每个处理器都有自己的高速缓存, 而它们又共享同一主内存, 当多个处理器的运算任务都涉及到同一个块主内存区域时, 将可能导致各自的缓存数据不一致.

1.2 缓存一致性问题
在这里插入图片描述
1.3 缓存一致性问题

1、一个CPU核心对数据的修改, 对其他CPU核心立即可见.
2、CPU修改数据, 首先是对缓存的修改, 然后再同步回主存, 在同步回主存的时候, 如果其他CPU也缓存了这个数据, 就会导致其他CPU缓存上的数据失效, 这样当其他CPU再去它的缓存读取这个数据的时候, 就必须从主存重新获取.
3、实现原理一般是基于CPU的MESI协议, 其中E表示独占Exclusive, S表示Shared, M表示Modify, I表示Invalid, 如果一个CPU核心修改了数据, 那么这个CPU核心的数据状态就会更新为M, 同时其他核心上的数据状态更新为I, 这个是通过CPU多核之间的嗅探机制实现的.

1.4 缓存一致性问题

Modify、Exclusive、Shared、Invalid, 当CPU写数据时, 如果发现操作的变量是共享变量, 即在其他CPU中也存在该变量的副本, 会发出信号通知其他CPU将该变量的缓存行为置为无效状态, 因此当其他CPU需要读取这个变量时, 发现自己缓存中缓存的该变量的缓存行是无效的, 那么它就会从内存中重新读取.

1.5 嗅探机制

原理分析:为了提高处理速度, 处理器不直接和内存进行通信, 而是先将系统内存的数据读到内部缓存(L1, L2或其它)后再进行操作, 但操作完就不知道何时会写到内存. 如果对声明了volatile的变量进行写操作, JVM会向处理器发送一条lock前缀的指令. 将这个变量所在的缓存行的数据写回到系统内存. 在多处理器下, 为了保证各个处理器缓存是一致的, 就会实现缓存一致性协议, 每个处理器通过嗅探在总线上传播的数据来检查自己的缓存的值是否过期, 当处理器发现自己缓存行对应的内存地址被修改, 就会将当前处理器缓存行设置成无效状态, 当处理器对这个数据进行修改操作的时候, 会重新匆匆系统内存中把数据读到处理器缓存里.

1.6 volatile单例模式

public class Instance {
    private static volatile Instance instance;
    private Instance() {}
    public static Instance getInstance() {
        if (instance == null) {
            instance = new Instance();
        }
    }
}

上面代码分成三步原子指令:
1、new指令申请内存;
2、在申请的内存中进行Instance的初始化;
3、将申请的内存地址的引用赋值给instance变量;
虽然volatile可以禁止指令重排序, 让上面三个指令有序执行, 但是问题是volatile并不能保证原子性, 所以上面代码中可能出现的问题是当Thread-A执行到第二步进行new Instance初始化时, 此时还没有将地址值赋给instance变量, 所以Thread-B此时看到的instance==null再次进入if中执行new Instance()操作.

1.7 volatile例子(主要用于理解多线程执行过程)

 static volatile boolean notice = false;

    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        // 实现线程A
        Thread threadA = new Thread(() -> {
            for (int i = 1; i <= 10; i++) {
                list.add("abc");
                System.out.println("线程A"+Thread.currentThread().getName()+"向list中添加一个元素,此时list中的元素个数为:" + list.size());
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (list.size() == 5) {
                    notice = true;
                }
            }
        });
        // 实现线程B
        Thread threadB = new Thread(() -> {
            while (true) {
                if (notice) {
                    System.out.println("线程B"+Thread.currentThread().getName()+"收到通知,开始执行自己的业务...");
                    break;
                }
            }
        });
        // 需要先启动线程B
        threadB.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 再启动线程A
        threadA.start();
    }

输出结果:
在这里插入图片描述
在这里插入图片描述
分析:首先每个线程有自己工作的内存两线程创建后会分别执行自己的任务,并不会说A执行完才能B执行。第一次线程A执行输出两条数据后,线程B又开始执行if(notice),由于此时线程A中if (list.size() == 5)并没有执行,所以此时notice为false。继续线程A在执行3次总共5次后执行

if (list.size() == 5) {
  notice = true;
 }

后notice变为true,由volatile内存共享,所以线程B得到notice为true 输出 “收到通知…” 最后把线程A剩下5条执行完。

如果把 static volatile boolean notice = false;
改为private boolean notice = false; 则输出如下:

在这里插入图片描述
并不会实现线程A和线程B对notice实时数据的共享

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读