Java volatile关键字-内存可见性、指令重排序

volatile关键字有两个作用:

  • 同一时刻内存可见性
  • 禁止指令重排序

大家都知道,计算机在执行程序时,每条指令都是在CPU中执行的,指令读取过程中,涉及到数据的读取和写入。由于程序运行过程中数据是放在主存中(物理内存),这里面存在一个问题,CPU执行速度远快于从内存中读取速度,因此如果对数据的读取操作都要通过和内存交换指令来进行的话,会大大降低指令的执行速度,因此CPU便引入了高速缓存。
Java volatile关键字-内存可见性、指令重排序_第1张图片

也就是说,当程序运行过程中,会将运算需要的数据从主存拷贝一份到CPU的高速缓存中,这样一来CPU计算时可以直接从它的高速缓存中读取和写入数据,运算结束后再将高速缓存的数据刷回主存当中。

每个CPU都有独自的高速缓存,在多核CPU中,每条线程可能运行于不同CPU中。假设初始值i = 0,多线程执行:i = i + 1,CPU1高速缓存中计算完i = 1并且还未将计算结果刷回主存的情况下,同一时刻CPU2将i = 0拷贝CPU2高速缓存中并完成计算,此时 i = i + 1被CPU1和CPU2分别计算了一次,期望i = 2,但结果却是i = 1。如果CPU计算完能立即将计算结果从其高速缓存刷回主存就能规避这个问题。volatile关键字修饰的变量便是解决了该问题,即同一时刻内存可见性。但是并不能保证原子性。要想程序正确执行,必须保证原子性、可见性以及有序性。原子性会在其它文章介绍。

一般来说,处理器为了提高程序运行效率,可能会对输入的代码进行优化,它不保证各个语句的执行先后顺序同代码中的顺序一致,但是它会保证执行结果和代码顺序执行的结果是一致的。这在单线程中不会构成问题,但是在多线程中会出现问题。非常经典的例子是在单例方法中同时对字段加入volatile关键字防止指令重排序。

例如:instance = new Singleton()并非原子操作,JVM中这句话大概做了以下3件事:

  1. 给instance分配内存
  2. 调用Singleton构造函数初始化成员变量,形成实例
  3. 将Singleton对象指向分配的内存空间

指令重排序的存在使得上面的第二步和第三步顺序是不能保证的,最终执行顺序可能是1-2-3,也可能是1-3-2。如果是后者,3执行完,2未执行,此时被线程2抢占,线程2读取到的instance已经不为null,但还未完成实例化的instance直接拿去用就会产生问题。所以此时需要加volatile关键字禁止指令重排序。

喜欢的朋友也可以选择关注我们的微信公众号,我们将持续输出干货
Java volatile关键字-内存可见性、指令重排序_第2张图片

你可能感兴趣的:(Java,java,多线程)