[讨论] 内存可见性问题
rxin2009
2014-03-09
请帮忙看下下面这段代码,3种不同的循环结束控制方案
public class VisiableTest { private static boolean stop = false; private static volatile boolean v2stop = false; private static HashMap<String, Boolean> map2stop = new HashMap<String, Boolean>(); /** * @param args * @throws InterruptedException */ public static void main(String[] args) throws InterruptedException { testMap(); testVolatile(); testBasicType(); } //程序正常结束 public static void testMap() throws InterruptedException{ map2stop.put("stop", Boolean.FALSE); new Thread(){ public void run(){ int i=0; while (!map2stop.get("stop") ){ i++; } System.out.println("map loop finish:"+i); } }.start(); Thread.sleep(1000); map2stop.put("stop", Boolean.TRUE); Thread.sleep(1000); System.out.println("map main stop"); } //说明了volatile的开销 public static void testVolatile() throws InterruptedException{ new Thread(){ public void run(){ int i=0; while(!v2stop){ i++; } System.out.println("volatile loop finish:"+i); } }.start(); Thread.sleep(1000); v2stop=true; Thread.sleep(1000); System.out.println("volatile main stop"); } //不可见,发生死循环 public static void testBasicType() throws InterruptedException{ new Thread(){ public void run(){ int i=0; while(!stop){ i++; } System.out.println("basic type loop finish:"+i); } }.start(); Thread.sleep(1000); stop=true; Thread.sleep(1000); System.out.println("basic type main stop"); } } 我的问题是,为什么boolean型值作为控制变量时不能结束循环,而将控制变量放在map中却可以结束循环呢? |
|
RednaxelaFX
2014-03-09
简单答案是:其实楼主的两种办法都不能可靠的保证可见性。
第一种写法:HashMap在不是并发安全的—即便这个例子里只有一个线程“写”、另一个线程“读”,不显式同步的话根据Java的语义其实是不保证可见性的。只不过楼主多半是在x86上测的,而x86的内存模型其实比Java的更强,所以有些理论上在Java里不保证正确的代码在x86上执行的时候观察不到错误。 在楼主测的时候它之所以不出错,除了在内存模型比Java更强的硬件上跑之外,还有一点就是JVM的JIT编译器没有把相关代码优化掉。这是因为map2stop不是局部变量,对它的访问得保守的认为有副作用,所以不能彻底优化掉。这样才留给x86有执行代码的机会。 第二种写法在HLLVM群组已经有详细分析了。请看看老帖。 http://hllvm.group.iteye.com/group/topic/34932 它的问题就是代码已经被JVM的JIT编译器彻底优化掉了,任凭x86的内存模型如何比Java的强也无济于事。 |
|
rxin2009
2014-03-09
RednaxelaFX 写道 简单答案是:其实楼主的两种办法都不能可靠的保证可见性。
第一种写法:HashMap在不是并发安全的—即便这个例子里只有一个线程“写”、另一个线程“读”,不显式同步的话根据Java的语义其实是不保证可见性的。只不过楼主多半是在x86上测的,而x86的内存模型其实比Java的更强,所以有些理论上在Java里不保证正确的代码在x86上执行的时候观察不到错误。 在楼主测的时候它之所以不出错,除了在内存模型比Java更强的硬件上跑之外,还有一点就是JVM的JIT编译器没有把相关代码优化掉。这是因为map2stop不是局部变量,对它的访问得保守的认为有副作用,所以不能彻底优化掉。这样才留给x86有执行代码的机会。 第二种写法在HLLVM群组已经有详细分析了。请看看老帖。 http://hllvm.group.iteye.com/group/topic/34932 它的问题就是代码已经被JVM的JIT编译器彻底优化掉了,任凭x86的内存模型如何比Java的强也无济于事。 不明白x86的内存模型,只了解jvm的内存模型;我理解的x86是指32位的cpu架构,是一个系列,但是我的机子是64位的(64-bit core i7),这个有区别吗,会影响到上面说的x86的内存模型吗? |
|
RednaxelaFX
2014-03-09
rxin2009 写道 不明白x86的内存模型,只了解jvm的内存模型;我理解的x86是指32位的cpu架构,是一个系列,但是我的机子是64位的(64-bit core i7),这个有区别吗,会影响到上面说的x86的内存模型吗?
嗯,这里说的x86是泛指,包括x86和x86-64(amd64,包括Intel的Intel 64) |
|
rxin2009
2014-03-09
RednaxelaFX 写道 rxin2009 写道 不明白x86的内存模型,只了解jvm的内存模型;我理解的x86是指32位的cpu架构,是一个系列,但是我的机子是64位的(64-bit core i7),这个有区别吗,会影响到上面说的x86的内存模型吗?
嗯,这里说的x86是泛指,包括x86和x86-64(amd64,包括Intel的Intel 64) 如果map作为局部变量还是可以正常结束的,如下测试代码: //程序正常结束 public static void testMap2() throws InterruptedException{ final HashMap<String,Boolean> map=new HashMap<String,Boolean>(); map.put("stop", Boolean.FALSE); new Thread(){ public void run(){ int i=0; while(!map.get("stop")){ i++; } System.out.println("map2 loop finish:"+i); } }.start(); Thread.sleep(1000); map.put("stop", Boolean.TRUE); Thread.sleep(1000); System.out.println("map2 main stop"); } ps:我是想跟着看下c2的编译过程,发现www.java.net下载个东西不容易呀,应该说是下不了,比如说fastdebug。 |
|
RednaxelaFX
2014-03-09
嘻嘻,楼上这样写的话对run()来说map仍然不是局部变量而是成员变量喔。
这是匿名内部类对环境的捕获。你反编译一下那个匿名内部类来看看就知道啦。 fastdebug版JDK现在没现成的了,得自己build。 |
|
rxin2009
2014-03-09
RednaxelaFX 写道 嘻嘻,楼上这样写的话对run()来说map仍然不是局部变量而是成员变量喔。
这是匿名内部类对环境的捕获。你反编译一下那个匿名内部类来看看就知道啦。 fastdebug版JDK现在没现成的了,得自己build。 呵呵,我刚才对基本数据类型的测试用例加了-Xint参数运行了下,不会死循环;那么这个问题可以理解为map的测试用例没有发生boolean的那种编译优化,但是为什么不优化呢,都是全局变量呀(把map作为全局变量的情况) |
|
gutigear
2014-04-08
Java 1.7.0_51 (client mode), windows XP
boolean型值作为控制变量时能结束循环 public class VTest { private static boolean stop = false; public static void main(String[] args) throws Exception { testBasicType(); } public static void testBasicType() throws InterruptedException{ new Thread(){ public void run(){ int i=0; while(!stop){ i++; } System.out.println("basic type loop finish:"+i); } }.start(); Thread.sleep(1000); stop=true; Thread.sleep(1000); System.out.println("basic type main stop"); } } basic type loop finish:1506739344 basic type main stop 没有附加JVM参数 |
|
RednaxelaFX
2014-04-08
gutigear 写道 Java 1.7.0_51 (client mode), windows XP
boolean型值作为控制变量时能结束循环 没有附加JVM参数 楼上试试用JDK8(jdk1.8.0 build 132)的Client VM在同一环境下跑这个测试再看看如何? 在打开-Xcomp之后就能看到不同的效果 ^_^ 这是因为JDK8的HotSpot VM的Client Compiler(C1)实现了循环不变量外提(loop invariant code motion)优化,所以在正常编译该例子的run()方法时能把stop的检查提到循环外面,这样就跟C2的状况一样了。 不打开-Xcomp看不到效果是因为这样跑该例子会让run()方法触发OSR编译而不是正常编译,正好干扰到C1实现的LICM优化了,于是stop的检查就还在循环里,于是还是能让循环结束。 |
|
gutigear
2014-04-09
没办法在同一环境下测试,java8不支持XP。。。java7用什么jvm参数可以实现循环不变量外提的优化效果?
另外我个人认为这是循环不变量优化的BUG,至少在这个场景中,这种优化是错误的。 RednaxelaFX 写道 gutigear 写道 Java 1.7.0_51 (client mode), windows XP
boolean型值作为控制变量时能结束循环 没有附加JVM参数 楼上试试用JDK8(jdk1.8.0 build 132)的Client VM在同一环境下跑这个测试再看看如何? 在打开-Xcomp之后就能看到不同的效果 ^_^ 这是因为JDK8的HotSpot VM的Client Compiler(C1)实现了循环不变量外提(loop invariant code motion)优化,所以在正常编译该例子的run()方法时能把stop的检查提到循环外面,这样就跟C2的状况一样了。 不打开-Xcomp看不到效果是因为这样跑该例子会让run()方法触发OSR编译而不是正常编译,正好干扰到C1实现的LICM优化了,于是stop的检查就还在循环里,于是还是能让循环结束。 |