遇到《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线程编程》这本书,翻译得有错误也就罢了,讲得是乱七八糟,没有条理,看得很费劲,跟没有一样,不过讲的点还可以。

解决方案 »

  1.   

    白啊
    你这个程序根本验证不了什么。T1都运行完了,你的T2才开始运行,这当然能够显示上次的结果了。volatile的意义是:
    比如两个线程在运行中。第一个线程已经初始化,将变量a载入了缓存后,这时候第二个线程改变了a的值。如果a没有用volatile修饰的话,那么可能在第一个线程中使用a时,还是使用的缓存中没有改变过的值。(这只是一个可能性,因为多线程的运行谁都不能保证结果是什么,这是与不同的系统有关的)。如果使用了volatile修饰,那么保证每次取a的值都不是从缓存中取,而是从a所真正对应的内存地址中取.
      

  2.   

    这本书我也买了,呵呵。volatile的意思是在使用被volatile修饰的变量的时候,必须到变量声明的地方重新取得变量的值,而不能使用本地存储的副本。这样如果使用了volatile,那么变量的变化可以被其他的使用者查看到。这就是书上说的意思。但是楼主的类TTT中使用了方法get和set。因为在使用get方法的时候,肯定也是到变量声明的地方重新取得的变量值。如果将变量声明成public,而不用get方法取值的话,就可以看到volatile的作用了。
      

  3.   

    gogon你才白呢,我是不会,你穷显摆啥呀to cuilichen:
      按你的说法,我把a声明为public,在线程t中,直接t2.a=23;然后读取的时候t2.a,也是23,为什么呀
      

  4.   

    说一下自己的理解,cpu在读取内存中的数据的时候,是先把它放在自己的cache里面的,这样如果有多个cpu的时候,多个线程可以并行的执行(不是并发),这样同一个变量就会放在多个cache里面,一个cpu修改了自己的cache,其他的cpu未必知道,使用了volatile,可能会强迫cpu每次更新数据的时候直接写内存(所谓的写直达),通知其他cpu更新自己的cache(通知其他cpu它们的cache已经无效,再从cache读数据的时候需要去更新)
    个人理解,可能不准确.大家可以去看看计算机系统结构
      

  5.   

    JMM中规定JVM为了提高多线程的内存访问效率采用了本地copy的方式在每个thread本地复制变量作为缓存。这样就造成了同步问题。如果加了volatile,thread会忽略这个优化,如果你对共享变量的对象加锁,同样可以避免这个问题,不过并发效率会更低。(synchronized加排他锁的时候,synchronized会独占对象,并且把所有共享变量复制到本地,放锁的时候会把thread local copy全部复制到共享变量。)The volatile modifier is used when you are working with multiple threads.The Java language allows threads that access shared variables to keep private working copies of the variables; this allows a more efficient implementation of multiple threads. These working copies need be reconciled with the master copies in the shared main memory only at prescribed synchronization points, namely when objects are locked or unlocked. As a rule, to ensure that shared variables are consistently and reliably updated, a thread should ensure that it has exclusive use of such variables by obtaining a lock that, conventionally, enforces mutual exclusion for those shared variables. 
      

  6.   

    to  DanielYWoo:
      没错,这个理论我也知道,但由此应该得出,如果不加volatile,一个线程修改一个对象的成员变量,另一个线程是见不到的吧。但我这个程序却能见到。
      

  7.   

    那是因为flush及时。其实volatile除了避免data race,还有很多情况下是避免re-order, 有些时候编译器会做优化比如
    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/
      

  8.   

    你说它被及时地flush了,难道多线程中的成员变量可以自动刷新?每个线程不是保留一份拷贝吗,它刷新的原理又是什么,什么时机。
      

  9.   

    没有声明volatile,只是说另一个线程的改变可能在这个线程中不可见。但不是必然的。所以你就算试验了1000次都是可见的,也不能说明什么,可能是flash的及时,也可能是你的机器环境的处理机制恰好是这样实现的。
    但java要保证的是在任何平台和环境下的正常运行。
    所以该声明volatile的时候,还是要声明的。
      

  10.   

    对于你的data race,我给你写了一个例子
    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
      

  11.   

    〉〉你说它被及时地flush了,难道多线程中的成员变量可以自动刷新?每个线程不是保留一份拷贝吗,它刷新的原理又是什么,什么时机。flush一定会发生访问volatile或者在release lock的时候。其他的时候系统不忙的时候也有可能,比如你的例子。