原子性、有序性、可见性
volatile可以保证有序性和可见性,不能保证原子性
在JMM中,如果一个操作执行的结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系。这里提到的两个操作既可以是在一个线程之内,也可以是在不同线程之间。
不管怎么重排序(编译器和处理器为了提高并行度),(单线程)程序的执行结果不能被改变。
编译器和处理器不会对存在数据依赖关系的操作做重排序,对不存在数据依赖关系的操作则可能进行重排序。
有如下这段代码:
public class Main {
private static volatile boolean initFlag = false;
public static void main(String[] args) throws Exception {
new Thread(() -> {
System.out.println("进入死循环,直到数据被修改");
while (!initFlag) {
}
System.out.println("成功检测到数据被修改,退出循环");
}).start();
Thread.sleep(2000);
new Thread(Main::prepareData).start();
}
public static void prepareData() {
System.out.println("准备修改数据");
initFlag = true;
System.out.println("修改数据完成");
}
}
-Xcomp -XX:+UnlockDiagnosticVMOptions -XX:+PrintAssembly -XX:CompileCommand=compileonly,/*Main.prepareData
由volatile修饰的共享变量进行写操作的时候多出一条带lock
前缀的汇编指令,如下所示:
lock前缀的指令在多核处理器下会:
对应JMM来说就是:
为了性能优化,JVM会在不改变数据依赖性的情况下,允许编译器和处理器对指令序列进行重排序(指令重排),而有序性问题指的就是程序代码执行的顺序与程序员编写程序的顺序不一致,导致程序结果不正确的问题。而加了volatile修饰的共享变量,则通过内存屏障解决了多线程下有序性问题
指令重排:在不影响单线程程序的执行结果的前提下,计算机为了最大限度发挥机器性能,会对指令进行重新排序(重排序也会遵循happens-before和as-if-serial原则)
内存屏障:内存屏障分为以下4类:
StoreLoad Barriers是一个“全能型”的屏障,它同时具有其他3个屏障的效果。现代的多处理器大多支持该屏障(其他类型的屏障不一定被所有处理器支持)。执行该屏障开销会很昂贵,因为当前处理器通常要把写缓冲区中的数据全部刷新到内存中(Buffer Fully Flush)。
volatile内存语义的实现:
在每个volatile写操作的前面插入一个StoreStore屏障
在每个volatile写操作的后面插入一个StoreLoad屏障
在每个volatile读操作的后面插入一个LoadLoad屏障
在每个volatile读操作的后面插入一个LoadStore屏障
不同CPU对于内存屏障规范实现的指令不一样,以Intel为例:
JVM底层对内存屏障的近似实现:
通过lock
指令。他不是内存屏障,但是他能完成类似内存屏障的功能
《阿里Java开发手册》为什么这样规定?
public class DCL {
public static /*volatile*/ DCL instance;
private DCL() {
}
public static DCL getInstance() {
if (instance == null) {
synchronized (DCL.class) {
if (instance == null) {
instance = new DCL(); //问题就出在这里
}
}
}
return instance;
}
}
一个对象的创建可以分为3步:
上面的2、3是有可能被重排序的,因为在单线程中对2、3进行重排序并不会影响最终的结果(intra-thread semantics 保证重排序不会改变单线程内的程序执行结果):
但是在多线程的钱情况下则可能发生问题:
这里A2和A3虽然重排序了,但Java内存模型的intra-thread semantics将确保A2一定会排在A4前面执行。因此,线程A的intra-thread semantics没有改变,但A2和A3的重排序,将导致线程B在B1处判断出instance不为空,线程B接下来将访问instance引用的对象。此时,线程B将会访问到一个还未初始化的对象:
对instance实例加上volatile关键字禁止指令重排以后则不会发生这样的问题了:
版权说明 : 本文为转载文章, 版权归原作者所有 版权申明
原文链接 : https://blog.csdn.net/qq_43613793/article/details/120398402
内容来源于网络,如有侵权,请联系作者删除!