一个Java内存可见性问题的分析
如果熟悉Java并发编程的话,应该知道在多线程共享变量的情况下,存在“内存可见性问题”:
在一个线程中对某个变量进行赋值,然后在另外一个线程中读取该变量的值,读取到的可能仍然是以前的值;
这里并非说的是时序的问题,即使在另外一个线程中循环读取该变量的值,也可能永远读不到该变量的最新值。
请看下面这段代码:
- public class Main extends Thread {
- private static boolean flag = false;
- @Override
- public void run() {
- while (!flag) {
- //System.out.flush();
- }
- }
- public static void main(String[] args) {
- Main m = new Main();
- m.start();
- try {
- Thread.sleep(200);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- flag = true;
- try {
- m.join();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("done");
- }
- }
这段代码在Windows(Java 7 HotSpot),Linux(Java 7 OpenJDK),MacOS(Java 7 HotSpot)上运行的时候根本停不下开;然而在Android(Dalvik)上,类似的代码则可以正常结束;我们知道,如果将变量flag声明为volatile的话,那么这段代码不管在哪个平台上运行都可以正常结束,事实也确实如此;这些平台都没有问题,它们的行为都符合JMM规范,只不过Android(Dalvik)的行为更保守一些而已。
疑惑在于,为什么是“永远不可见”?我之前一直以为“内存可见性问题”只是时间长短而已。
更诡异的是,如果将while循环中的System.out.flush()打开的话,程序又都可以正常结束了,这又是什么原因呢?
首先,我们从字节码入手,发现它们对应的字节码基本上是一样的;即使是volatile版本,也只不过是在变量上增加了一个volatile标记,字节码并无不同。
据此,我们可以推断,差异可能来源于JIT,于是关掉JIT(如何控制JVM中的JIT行为?),果然,这些代码又都可以正常结束了。
按照我之前学习到的一些有关多核CPU方面的知识,多核CPU的行为并不会导致“永远不可见”的问题,理由如下:
1.如果是CPU缓存,多核CPU之间存在“缓存一致性”协议,所以这里并不会导致“不可见”的问题;
2.如果是CPU Store Buffer,因为容量有限,迟早会写回到缓存,所以这里并不会导致“永远不可见”的问题;
3.如果是CPU指令重排序,由于这段代码是在一个循环中读取变量的值,所以这里不会有任何影响。
那么,问题就只能出在JIT生成的代码上了,让我们查看一下JIT生成的代码(如何控制JVM中的JIT行为?):
这个是无volatile无System.out.flush()的版本,它不能停止,说明如下:
第一个红色标记,读取flag的值
第二个红色标记,判断flag的值是否为false,如果是则顺序执行到第三个红色标记处
第三个红色标记,这里是一个死循环
从这里可以看出,JIT对生成的代码做了高度优化,它认为代码中没有地方对flag进行修改,因此直接生成一段死循环代码,避免反复读取flag的值以提升性能,但是这违背了这段代码的原意,导致程序不能停止。
这个是有volatile的版本,它可以正常结束,说明如下:
第一个红色标记,读取flag的值
第二个红色标记,判断flag的值是否为false,如果是则跳转到第个红色标记处
这完全符合这段代码的原意,因此可以正常结束。
这个是有System.out.flush()的版本,从红色标记处可以看出,这里也完全符合代码原意,因此可以正常结束;由于某种原因,JIT没有对生成的代码进行优化。
至此,疑惑已完全解开,在此也顺便总结一下Java中的volatile关键字:
1.阻止Java编译器对字节码进行重排序(似乎没有Java实现在字节码层面进行重排序)
2.在JIT生成的代码中插入适当的内存屏障指令
3.禁止JIT过度优化生成的代码
3.字节码层面并不会关心volatile(变量标记除外),执行引擎和JIT应该关心
一个Java内存可见性问题的分析的更多相关文章
- 从一个小例子引发的Java内存可见性的简单思考和猜想以及DCL单例模式中的volatile的核心作用
环境 OS Win10 CPU 4核8线程 IDE IntelliJ IDEA 2019.3 JDK 1.8 -server模式 场景 最初的代码 一个线程A根据flag的值执行死循环,另一个线程B只 ...
- Java内存模型JMM简单分析
参考博文:http://blog.csdn.net/suifeng3051/article/details/52611310 http://www.cnblogs.com/nexiyi/p/java_ ...
- Java内存可见性
如果一个线程对共享变量的修改,能够被其它线程看到,那么就能说明共享变量在线程之间是可见的.如果一个变量在多个线程的工作内存中都存在副本,那么这个变量就是这几个线程的共享变量.Java内存模型(Java ...
- Java内存可见性volatile
概述 JMM规范指出,每一个线程都有自己的工作内存(working memory),当变量的值发生变化时,先更新自己的工作内存,然后再拷贝到主存(main memory),这样其他线程就能读取到更新后 ...
- java 内存可见性
java线程 -> 线程工作内存 -> 主物理内存 线程工作内存的原理是栈内是连续的小空间,寻址速度比堆快得多,将变量拷贝到栈内生成副本再操作 什么是重排序 代码指令可能并不是严格按照代码 ...
- 一个java内存泄漏的排查案例
这是个比较典型的java内存使用问题,定位过程也比较直接,但对新人还是有点参考价值的,所以就纪录了一下. 下面介绍一下在不了解系统代码的情况下,如何一步步分析和定位到具体代码的排查过程 (以便新人参考 ...
- java内存问题排查及分析
最近了解了一下jdk对于jvm分析工具的使用,下面通过一个简单的列子介绍一下,以下内容部分来自其他帖子. 下面这段代码明显有问题(从网上抄的) import java.util.HashMap; im ...
- CognitiveJ一个Java的人脸图像识别开源分析库
CognitiveJ 是一个开源的,支持 Java 8 API 的库,用于管理和编排 Java 应用和微软的Cognitive(Project Oxford)机器学习和图像处理库的项目,可以让你查询以 ...
- java 内存 线程 类 vm分析工具
JMeter.Jconsole.JVMStat
随机推荐
- XlFileFormat Enumeration
https://msdn.microsoft.com/zh-cn/library/office/ff198017.aspx
- SQL Server数据库性能优化之索引篇【转】
http://www.blogjava.net/allen-zhe/archive/2010/07/23/326966.html 性能优化之索引篇 近期项目需要, 做了一段时间的SQL Server性 ...
- JUnit 4 单元测试
Individual Project ——JUnit 4 单元测试 学习到JUnit单元测试,我拿来测试之前写过的一个计算器(两个依存类:Calc.java CalcFunction.java).代码 ...
- 轮播图适应代码jQ
(function(){ var i = 0; var time ; $('.page-size').html('1'); var obj = $('.xst-scroll>li'); var ...
- 为dom添加点击事件,由此引发this指向的思考
下午没有任务,闲来无事仿个小网页巩固下基础知识.由于公司安全规定,原网页截图不便上传(也没法上传),回家后做了个简单的菜单以图示: 目标:点击某选项时,该选项底边加粗 1.首先定义click方法,然后 ...
- STM32F之IAR6.5 J-Link程序下载错误
错误01:Keil环境下使用J-Link SW模式下载程序,而IAR6.5则出现如图1-1的错误.
- C++ 之 新式转型操作符
四种新式转型: const_cast.dynamic_cast.reinterpret_cast.static_cast!! 1.const_cast : 去除常量性 2.dynamic_cast ...
- 算法-QuickSort
#include <stdlib.h> #include <iostream> #include <vector> using namespace std; tem ...
- post和get请求
get请求:不安全,参数在url地址中的参数的长度不能大于1024字节 post请求:安全,参数都是凤凰族昂在data里的,参数长度不限
- 软件测试第六周学习笔记之“Win8 APP应用程序的白盒测试”
这周的学习笔记我想写点自己关于实验中碰到的问题和感想. 因为这次做的是白盒测试,所以我决定去测试一下上回测试的app的功能函数. 这次我用的是单元测试项目来做的白盒测试: 创建单元测试的步骤: 1.点 ...