volatile关键字的作用,指令重排问题

您所在的位置:网站首页 指令重排作用 volatile关键字的作用,指令重排问题

volatile关键字的作用,指令重排问题

2024-07-08 23:05| 来源: 网络整理| 查看: 265

java中volatile关键字的作用及原理。volatile可以保证内存可见性、程序执行的有序性。

1、保证内存可见性

如下图所示,在jvm中,所有线程的共享变量都存在主内存中,然后每个线程有自己独有的工作内存,每个线程不直接操作主内存中的数据,而是将主内存中的变量变成一个副本存在自己的工作内存中,线程只操作自己工作内存中的变量。 在这里插入图片描述 在单线程中不会出现问题,多线程的时候就可能出现脏数据!例如: int i = 1; AB两个线程同时拿到了变量 i,进行递增操作,AB两个线程都将i放到自己工作内存中进行+1操作,而此时A线程操作了 i 之后,还没有将结果刷新到主内存中,此时B也对 i 在自己的工作内存中进行了+1操作,然后AB两个线程都将自己的结果刷新到主内存中,结果就是i只加了1,这样就有问题了。原因就是B线程读取到了变量 i 的脏数据。

解决的话就是对变量 i 加上volatile关键字,volatile可以保证当A线程对 i 的值做了修改后会立即刷新到主内存中,是对其他线程可见的,其他线程读取到的值也就作废,强迫重新从主内存中重新读取,这样在任何时刻,多个线程都能看的最新的i的值。值对应每个线程都是可见的。

2、内存可见性原理

volatile是通过汇编加上Lock前缀指令,触发底层的MESI缓存一致性协议来实现的。这个协议有很多种,主要的就是MESI。MESI表示4中状态: 在这里插入图片描述 比如说当前有一个A cpu从主内存中拿到了一个变量X,初始值=1,放到了自己的工作内存中,此时它的状态就是独享状态E。然后另外一个B cpu也拿到了X放到自己的工作内存中,此时之前的A CPU就是监听内存总线,发现这个X被多个cpu获取了,这个时候AB两个CPU获取到的X的状态就是共享状态S。 然后A cpu将X从自己工作内存中拿到ALU计算单元进行计算,返回2,接着就会告诉内存总线,将此时X的状态修改为M。B cpu也会不断监听内存总线,发现这个X已经被别的CPU修改了状态,自己内部的值就会被改为 I 无效状态,等A cpu将修改后的值刷新到主内存后再重新获取X的值。裁决谁先修改X的值由CPU通过硬件在同一个指令周期内决定,裁定是谁修改,另外的就无效。 当然MESI也会有失效的时候,缓存的最小单元是缓存行,如果当前共享的数据超过了缓存行的长度,就会使MESI协议失效,此时触发总线加锁机制,第一个CPU拿到后,其他的都不能获取。

3、有序性,禁止指令重排

指令重排是JVM(包括CPU)为保证效率可能会对指令进行重新排序执行。例如,只要代码移动不会改变程序的语义,当编译器认为程序中移动一个写操作到后面会更有效的时候,编译器就会对代码进行移动。JVM可以保证在单线程下指令重排后和重排前执行结果一样,可是多线程的情况就无法保证了。最典型的例子就是单例双重锁机制:

public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton();// 这里不是原子性操作 } } } return instance; }

instance变量由volatile 修饰,如果不加这个修饰符,会有问题,虽然加了synchronized 锁 ,多线程下不能保证代码执行的正确性。因为 instance = new Singleton(); 这并不是一个原子性的操作,这里其实执行了三步:

1、为instance 分配内存2、初始化instance3、将instance 变量指向分配的内存空间

由于指令重排的问题,第2步和第3步没有依赖关系,就可能造成第3步在第2步之前执行。也就是说线程A进来判断为null,进入方法执行了instance = new Singleton(); 有可能JVM先执行了第3步,这个时候线程B进来了,此时instance 已经不是null,属于半初始化状态,线程B拿到了一个半成品,虽然开发中很少见,但是确实会存在这种情况。加上volatile关键字之后就会保证instance = new Singleton();不会进行指令重排,会按照顺序执行,这样就不会有问题。

4、有序性的原理–>内存屏障

volatile的有序性是通过内存屏障实现的。JVM和CPU都会对指令做重排优化,所以在指令间加入了一个屏障点,告诉JVM和CPU不能进行指令重排。具体分为4种:

1、读读;2、读写;3、写读;4、写写屏障点的插入策略: 在这里插入图片描述 原文参考:https://blog.csdn.net/weixin_30342639/article/details/91356608


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3