不加volatile 变量,两个线程也是可见同一个变量的?

小乞丐 发布于 2016/04/16 22:00
阅读 758
收藏 0
package com.test;


public class Test extends Thread{

	private  boolean is = true ;
	
	@Override
	public void run() {
		System.out.println("run : " + Thread.currentThread().getId());
		while(is){
			System.out.println("11");
		}
	}
	 
	public void setis(){
		System.out.println("set : " + Thread.currentThread().getId());
		is = false;
	}
	
	public static void main(String[] args) {
		 Test t  = new Test();
		 t.start();
		  try {
			Thread.sleep(1000);
		} catch (InterruptedException e) {
			e.printStackTrace();  
		}
		 t.setis();
	}
}



运行结果实际上是会退出循环的,这个 

Java 内存模型和线程规范 以及 《Effective Java中文版 第2版》 描述不相符合。

我测试版本为jdk7.

另外可以看到字节码信息,在run中循环处,getfield 与 setis 方法中 putfield 操作的是同一个变量,但是他们在不同的线程中,这个描述似乎也不与上述两本书中描述的相同。

请问这是怎么回事? 理论上应该是循环永远不会退出才对呀。/


运行结果:

run : 9
11
11
set : 1



加载中
0
景愿
景愿
不加volatile变量并不意味着永不更新,只是副本延迟而已,以现在CPU的速度,你需要在高并发的情况下才能偶尔发现线程副本延迟更新的发生
0
小乞丐
小乞丐

引用来自“景愿”的评论

不加volatile变量并不意味着永不更新,只是副本延迟而已,以现在CPU的速度,你需要在高并发的情况下才能偶尔发现线程副本延迟更新的发生

这个不对,如果在主线程中循环,在子线程中改变is 的值,就会无限循环。

package com.test;




public class Test extends Thread{


private static boolean is = true ;
 
 
public static void main(String[] args) {

 
while(Test.is){
System.out.println("11");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();  

}
}
 
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();  

}
new Thread(new Runnable() {

@Override
public void run() {

is = false;

}
}).start();

}
}

所以这个种情况肯定是没有更新副本。



景愿
景愿
如果在另一个线程副本中更改主内存变量,那么先要从线程中发起store最后在传输到主内存中,然后从另一个线程读取值,你方式是直接修改主内存变量,两者处理方式有很大差别
0
JunOnes
JunOnes

你这个实现有问题,和书上的不一样。


import java.util.concurrent.TimeUnit;

public class StopThread {
	private static boolean stopRequested;
	
	public static void main(String[] args) throws InterruptedException {
		Thread backgroudThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested) {
					i++;
				}
			}
		});
		backgroudThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}


你跑下这段代码,确实是死循环。

0
小乞丐
小乞丐

引用来自“ZhouJunhua”的评论

你这个实现有问题,和书上的不一样。


import java.util.concurrent.TimeUnit;

public class StopThread {
	private static boolean stopRequested;
	
	public static void main(String[] args) throws InterruptedException {
		Thread backgroudThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested) {
					i++;
				}
			}
		});
		backgroudThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}


你跑下这段代码,确实是死循环。

这个一样会退出循环,只要是在子线程中循环,都会退出。

0
小乞丐
小乞丐

引用来自“ZhouJunhua”的评论

你这个实现有问题,和书上的不一样。


import java.util.concurrent.TimeUnit;

public class StopThread {
	private static boolean stopRequested;
	
	public static void main(String[] args) throws InterruptedException {
		Thread backgroudThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested) {
					i++;
				}
			}
		});
		backgroudThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}


你跑下这段代码,确实是死循环。

我发现只要在

 while(!stopRequested) {
                   i++;
               }

中加上任何代码就会退出循环,这个是为什么?貌似解释不通呀。

 while(!stopRequested) {
                   i++;

            System.out.println(i);
               }

这个就会退出循环。

FuYung
FuYung
System.out.println(i);里面有synchronized关键字
0
小乞丐
小乞丐

引用来自“景愿”的评论

不加volatile变量并不意味着永不更新,只是副本延迟而已,以现在CPU的速度,你需要在高并发的情况下才能偶尔发现线程副本延迟更新的发生

引用来自“小乞丐”的评论

这个不对,如果在主线程中循环,在子线程中改变is 的值,就会无限循环。

package com.test;




public class Test extends Thread{


private static boolean is = true ;
 
 
public static void main(String[] args) {

 
while(Test.is){
System.out.println("11");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();  

}
}
 
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();  

}
new Thread(new Runnable() {

@Override
public void run() {

is = false;

}
}).start();

}
}

所以这个种情况肯定是没有更新副本。



我发现这里进入了一个矛盾的状态或者说是解释不通的状态.

并不是什么主线程、次线程的概念。

理论上一个线程A,从主内存中读取到一个变量,在修改后刷新至主内存之前被线程B 读取了该变量,

那么线程A改变变量后,刷新至住内存是不会影响到线程B的。

然而从jvm操作指令上可以看出,线程B的每次循环都是进行了getfield操作,这个操作获取的应该是当前线程B的栈中的值,这个操作是不会去主内存获取变量的,那么线程B的getfiled操作应该就是第一load的值。

然而上诉情况一种会不会无限循环、一种会,似乎和理论上线程变量操作矛盾了.

0
iBoxDB
iBoxDB
不能有 synchronized 关键字,这个关键字会自动刷新副本。out.println()是线程安全函数,内部会调用相关同步指令。
0
小乞丐
小乞丐

引用来自“iBoxDB”的评论

不能有 synchronized 关键字,这个关键字会自动刷新副本。out.println()是线程安全函数,内部会调用相关同步指令。

有道理,但是既然刷新主内存了,下面也应该会退出循环呀,可是这个也有out.println 为什么不退出循环呢?

private static boolean is = true;
public static void main(String[] args) {
while(test1.is){
System.out.println("11");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();  
}
}
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();  

}
new Thread(new Runnable() {

@Override
public void run() {

is = false;

}
}).start();

}

iBoxDB
iBoxDB
你确认 new Thread(new Runnable() {} 这句执行了?
0
小乞丐
小乞丐

引用来自“iBoxDB”的评论

不能有 synchronized 关键字,这个关键字会自动刷新副本。out.println()是线程安全函数,内部会调用相关同步指令。

引用来自“小乞丐”的评论

有道理,但是既然刷新主内存了,下面也应该会退出循环呀,可是这个也有out.println 为什么不退出循环呢?

private static boolean is = true;
public static void main(String[] args) {
while(test1.is){
System.out.println("11");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();  
}
}
try {
Thread.sleep(1 * 1000);
} catch (InterruptedException e) {
e.printStackTrace();  

}
new Thread(new Runnable() {

@Override
public void run() {

is = false;

}
}).start();

}

package com.test;




public class Test {
private static boolean is = true ;
public static void add(){
int i= 0;
while(Test.is){
i++;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();  
}
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
add();
}
}).start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();  
}
is = false;
}
}



在循环中添加:

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();  
}

这个也会退出循环。sleep 方法也会刷新主内存吗? 哪里可以看出来呢?

0
小乞丐
小乞丐

引用来自“ZhouJunhua”的评论

你这个实现有问题,和书上的不一样。


import java.util.concurrent.TimeUnit;

public class StopThread {
	private static boolean stopRequested;
	
	public static void main(String[] args) throws InterruptedException {
		Thread backgroudThread = new Thread(new Runnable() {
			
			@Override
			public void run() {
				int i = 0;
				while(!stopRequested) {
					i++;
				}
			}
		});
		backgroudThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequested = true;
	}
}


你跑下这段代码,确实是死循环。

引用来自“小乞丐”的评论

我发现只要在

 while(!stopRequested) {
                   i++;
               }

中加上任何代码就会退出循环,这个是为什么?貌似解释不通呀。

 while(!stopRequested) {
                   i++;

            System.out.println(i);
               }

这个就会退出循环。

 System.out.println(i); 换成 thread.sleep 也会退出循环。

 thread.sleep 为什么也会刷新内存呢?


JunOnes
JunOnes
sleep 会让出cpu,当激活的时候会重新读取变量,那时就更新了
返回顶部
顶部