java 内存模型 (jmm)和jvm运行区区别
java内存模型是Java语言规范(提出规则) 描述多线程程序的规则
规范了Java虚拟机与计算机内存是如何协同工作的。Java虚拟机是一个完整的计算机的一个模型,因此这个模型自然也包含一个内存模型——又称为Java内存模型(用来屏蔽掉java程序在各种不同的硬件和操作系统对内存的访问的差异,这样就可以实现java程序在各种不同的平台上都能达到内存访问的一致性。)。参考http://ifeve.com/java-memory-model-6/ https://www.jianshu.com/p/15106e9c4bf3
jvm运行区(具体体解决规范)就是堆,方法区,栈
多线程中的问题:
- 所见非所得
- 无法肉眼去检查程序的准确性
- 不同的运行平台有不同的表现
- 错误很难重现
所见非所得问题如下代码:
public class Demo1Visibility {
int i = 0;
boolean isRunning = true;
public static void main(String args[]) throws InterruptedException {
Demo1Visibility demo = new Demo1Visibility();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("子线程here i am...");
while(demo.isRunning){
demo.i++;
}
System.out.println(demo.i);
}
}).start();
Thread.sleep(3000L);
demo.isRunning = false;
System.out.println("主线程shutdown...");
}
}
预想结果为:
子线程here i am…
主线程shutdown…
2125892746
真实结果为:
其调整不同的jdk值其i的打印值为:
我们可以看到【i】的值没有打印,而且线程还没有执行结束,那是为什么呢? 那我们一起来一步一步的分析推理下,我们先把JMM逻辑图画一下,如下图:
- 主线程main和子线程是默认读取主内存中isRunning=true
- main更改其isRunning=false
- 分析知要吗就是主线程的isRunning改变的值没及时存入,要么就是子线程没立刻读取到内存变化的值。
- 判断是否是cpu高速缓存的原因吗?由于主线程睡眠了3秒(我们都知道CPU的高速缓存运行速度比内存要快的多)所以排除CPU的高速缓存原因
因。
我们先分析该代码执行过程图:
通过上面的分析知道出现与预期的结果不一样的是jit编译器的原因。通过云课堂的学习我们知道 JIT编译器在执行的时候会 遵循as-if-serial语义
会对代码进行指令重排。
指令重排如下图:
如上图单独一个线程的其值是不会变的,但是2个现场的时候就矛盾了。
解决方案
我们这里用到关键字volatile(保证修饰的值可见性,使其修改的值永远都是取到最新的值)
volatile boolean isRunning = true;
其原理我们先用运行上面实例.claas进行反编译 javap -v -p Demo1Visibility.class
可以看大下面有这个代码
我们看到 用volatile修饰的isRunning变量反编译后 看到访问控制加了个ACC_VOLATILE标识符
查看oracle虚拟机https://docs.oracle.com/javase/specs/jvms/se8/html/jvms-4.html#jvms-4.3.2
我们可以看到ACC_VOLATILE的作用就是不允许JIT编译器进行缓存到此我们也整理下volatile关键字吧
- 可见性问题:让一个线程对共享变量的修改,能够及时的被其他线程看到。
- java内存模型规定:
- 对volatile变量v的写入,与所有其他线程后续对v的读同步
要满足这些条件,所以volatile关键字就有这些功能:
禁止缓存: volatile变量的访问控制符会加个 ACC_VOLATILE
————————————————
版权声明:本文为CSDN博主「weixin_41357524」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/weixin_41357524/article/details/104629010