[讨论] 内存可见性问题

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的检查就还在循环里,于是还是能让循环结束。

Global site tag (gtag.js) - Google Analytics