遇到《java线程编程》中讲到volatile的例子,是说加上这个关键字,各个线程能够共享一个变量的值,否则,一个线程修改了,其他线程看不到变化。但我自己写了个小例子却是可以看到的,为什么?是我理解有误吗
package thread;/**
* Created by IntelliJ IDEA.
* User: Administrator
* Date: 2006-1-16
* Time: 22:23:25
* To change this template use File | Settings | File Templates.
*/
public class MyTestVolatile { public static void main(String args[]){
final TTT t2=new TTT();
Thread t=new Thread(){
public void run() {
t2.setA(23);
System.out.println("a="+t2.getA()); System.out.println("这是在线程t中");
}
};
t.start(); Thread tt=new Thread(){
public void run() {
// t2.setA(23);
System.out.println("a="+t2.getA()); System.out.println("这是在线程t2中");
}
};
tt.start();
try {
tt.join();
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
System.out.println("main="+t2.getA());
}
}class TTT{
private int a;//没加volatile,照样t修改了a,在t2中可以见到,在main线程中亦可,奇怪啊!
public void setA(int a){
this.a=a;
}
public int getA(){
return a;
}
}另外,建议大家不要买《java线程编程》这本书,翻译得有错误也就罢了,讲得是乱七八糟,没有条理,看得很费劲,跟没有一样,不过讲的点还可以。
package thread;/**
* Created by IntelliJ IDEA.
* User: Administrator
* Date: 2006-1-16
* Time: 22:23:25
* To change this template use File | Settings | File Templates.
*/
public class MyTestVolatile { public static void main(String args[]){
final TTT t2=new TTT();
Thread t=new Thread(){
public void run() {
t2.setA(23);
System.out.println("a="+t2.getA()); System.out.println("这是在线程t中");
}
};
t.start(); Thread tt=new Thread(){
public void run() {
// t2.setA(23);
System.out.println("a="+t2.getA()); System.out.println("这是在线程t2中");
}
};
tt.start();
try {
tt.join();
} catch (InterruptedException e) {
e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates.
}
System.out.println("main="+t2.getA());
}
}class TTT{
private int a;//没加volatile,照样t修改了a,在t2中可以见到,在main线程中亦可,奇怪啊!
public void setA(int a){
this.a=a;
}
public int getA(){
return a;
}
}另外,建议大家不要买《java线程编程》这本书,翻译得有错误也就罢了,讲得是乱七八糟,没有条理,看得很费劲,跟没有一样,不过讲的点还可以。
你这个程序根本验证不了什么。T1都运行完了,你的T2才开始运行,这当然能够显示上次的结果了。volatile的意义是:
比如两个线程在运行中。第一个线程已经初始化,将变量a载入了缓存后,这时候第二个线程改变了a的值。如果a没有用volatile修饰的话,那么可能在第一个线程中使用a时,还是使用的缓存中没有改变过的值。(这只是一个可能性,因为多线程的运行谁都不能保证结果是什么,这是与不同的系统有关的)。如果使用了volatile修饰,那么保证每次取a的值都不是从缓存中取,而是从a所真正对应的内存地址中取.
按你的说法,我把a声明为public,在线程t中,直接t2.a=23;然后读取的时候t2.a,也是23,为什么呀
个人理解,可能不准确.大家可以去看看计算机系统结构
没错,这个理论我也知道,但由此应该得出,如果不加volatile,一个线程修改一个对象的成员变量,另一个线程是见不到的吧。但我这个程序却能见到。
class TTT {
int x = 0;
int y = 0;
public void writer(int i) {
x = i;
y = i;
}
public void reader() {
if (y > x) {
System.out.println("reorder:" + y + ":" + x);
}
}
}
如果有两个线程同时访问这个对象,一个不停的ttt.writer(i++), 一个不停的ttt.reader(), writer方法如果表面看来应该是按顺序执行x=i, y=i,但是如果编译器优化后或者被某些处理器在运行时可能re-order成为y=i, x=i的执行顺序(理论上有可能这样,如果是一件简单的setter,有可能被编译器认为是可以做自动内联,相当于C++的inline函数,来减少栈操作,如果这种情况发生,编译器的优化动作甚至会更大。在我的P4双核 3Ghz Intel上用Sun JDK 1.4.2/1.5.0写了几个例子,还没发现过,用javap看p-code太烦,而且p-code只能看出来编译器做的手脚,看不到处理器的手脚,没时间查了),这样就会在某一个时间点下发生reader里面y>x的情况。volatile可以避免re-order。在老的JMM里,非volatile变量和volatile变量的混合运算访问会把原来volatile的变量降级到非volatile,新的JMM强化了volatile的re-order限制,基本等于synchronized了。synchronzied, volatile, final, double check这些东西和JMM关系非常复杂,其实我了解也不是深入,因为很少在实际中遇到。你看看一些关于JSR133, JMM的文章吧
http://www.cs.umd.edu/~pugh/java/memoryModel/
但java要保证的是在任何平台和环境下的正常运行。
所以该声明volatile的时候,还是要声明的。
public class MyTestVolatile {
volatile static int test1 = 0;
//static int test1 = 0; public static void main(String args[]) {
Thread t1 = new TestThread("test1");
Thread t2 = new TestThread("test2");
t1.start();
t2.start();
try {
t1.join();
t2.join();
} catch (Exception e) {
e.printStackTrace();
System.exit(-1);
}
System.out.println(MyTestVolatile.test1);
}
}class TestThread extends Thread
{
public TestThread(String n) {
super(n);
}
public void run() {
//synchronized (MyTestVolatile.class) {
for (int i = 0; i < 100000000; i++) {
int oldV = MyTestVolatile.test1;
MyTestVolatile.test1++;
int newV = MyTestVolatile.test1;
if (newV - oldV > 1 ) {
System.out.println("found");
}
}
//}
System.out.println(this.getName() + " thread end " + MyTestVolatile.test1);
}
}首先要说明的是
TestThread会对共享变量test1做1000000次以下操作:
int oldV = MyTestVolatile.test1;
MyTestVolatile.test1++;
int newV = MyTestVolatile.test1;如果是单线程,肯定每次循环都是oldV+1=newV,而且最终肯定MyTestVolatile.test1=1000000
如果是在多线程下,如果你不加volatile,那么由于MyTestVolatile.test1的local copy做++操作,在大多数时候没有来得及flush的时候,两个线程保持不可见彼此的local copy,所以每个线程内大多数时候肯定每次循环都是oldV+1=newV,所以你运行不加volatile的这个程序肯定绝大多数时候都不会执行System.out.println("found");
但是如果你用volatile的版本,你会发现因为每次写操作都要在下一个读操作前flush进master copy,所以出现found的概率大大提高。
如果你更严格,打开synchronized的那个注释,这样对class级别的排他锁不仅达到了volatile的作用,而且由于完全排他,导致MyTestVolatile.test1++的操作不会被并发,所以最终结果是绝对的200000,而且绝对永远不会出现found