package net.jcip.examples;public class NoVisibility {
static boolean ready;
static int number; private static class ReaderThread extends Thread {
public void run() {
while (!ready) {
Thread.yield();
}
System.out.println(number);
}
} public static void main(String[] args) throws InterruptedException {
new ReaderThread().start();
// 这个是我特意加的,让上面的线程先跑起来
Thread.sleep(1000);
number = 41;
ready = true;
}
}
上面这段代码是书上的,书上说这段代码中的while可能会一直循环,因为对于读线程来说,ready的值可能永远不可见。甚至更奇怪的
现象是,NoVisibility可能会打印0,因为早在对number赋值之前,主线程就已经写入ready并使之对读取线程可见。可是我运行下来,始终打印出41,加不加sleep那段话都一样。什么原因呢?

解决方案 »

  1.   

    我按照楼主的代码试验了一下,确实如楼主所说,第一,ready并非不可见,实际上是可见的,第二,执行结果确实是41。
    我把代码修改了一下,如下:package queston2;
    public class NoVisibility {
        static boolean ready;
        static int number;    private static class ReaderThread extends Thread {
            public void run() {
             System.out.println(ready);
                while (!ready) {
                    System.out.println("Start to yield!");
                 Thread.yield();
                 System.out.println("Restore from yield!");
                }
                System.out.println(number);
            }
        }    public static void main(String[] args) throws InterruptedException {
            new ReaderThread().start();
            // 这个是我特意加的,让上面的线程先跑起来
            Thread.sleep(1000);
            System.out.println("Main thread Sleep end");
            number = 41;
            ready = true;
        }
    }其结果是:
    false
    Start to yield!
    Restore from yield!
    ...
    ...
    ...
    Start to yield!
    Main thread Sleep end
    Restore from yield!
    41以上的运行结果说明,程序先从main函数开始运行,main是一个线程,然后main函数新建了ReaderThread方法所在线程,然后main函数执行了sleep(1000).
    ReaderThread方法中ready是明显可见的,因为运行结果第一行中有false输出。不过这个false并不是main函数赋值的,而是JVM对NoVisibility类的静态成员变量赋的初值。
    因为ready的值此时为false,说以while(!ready){Thread.yield()}肯定是可以运行的。运行后ReaderThread进入yield状态,根据JAVA的线程规则,处于yield状态的线程会把运行权让给同样优先级的main线程,可是此时main线程正在执行sleep(1000)中,还不能运行,因此又将执行权交回ReaderThread进程,由于main在sleep(1000)结束以前都不会改变ready的值,因此while(!ready)依然为真,继续yield(),如此一直循环。所以结果中有重复的 Start to yield!/Restore from yield!
    直到main函数从sleep(1000)中恢复,这时ReaderThread线程依然会不停地yield,因此同样优先级的main线程便执行number = 41;ready = true;等代码,改编了成员变量的值,然后main线程就结束了。此时ReaderThread依然yield,但是由于main线程已经结束,就剩下ReaderThread自己,再没有别的线程可让了。因此ReaderThread线程执行while(!ready),这是ready已经让main线程修改为ture,所以while(!ready)为假,这时就跳出了while循环执行打印number的值,这是打印41.
    执行结果就是这样。
    结论:
    ready是在ReaderThread是可见的,因此说ready不可见不对。
    while()循环确实是要执行很多次,但是由于while循环的条件受main线程的影响,因此是有结束的。
      

  2.   

    谢谢你的劳动,我用的是jdk1.6,我不知道是不是版本太新,书太老的问题
      

  3.   

    我希望能有一段代码:可以展现出不可见性的一面。
    也就是线程A改了某个变量a,线程B却不知道变量a已经改了的情况。
      

  4.   

    java的语言级好像没有对线程的变量的保护机制吧?其实多线程编程容易造成的问题不就是说不同的线程在互不知情的情况下可以对同一变量进行修改么。我知道的是如果要对一个变量进行保护,最好吧这个变量放在一个对象中,然后用synchronized对该对象加锁,这样可以保证A改变变量a的时候,变量B无法改变变量a。或者可以直接对修改变量的函数sychornized,不过这样的话会对多线程的效率产生一定的影响。
      

  5.   


    同步的话,线程B肯定能知道a变量已经改变了,以为此时线程A已经修改了变量a,并且已经释放了锁.
    我所说的是不同步,而且两个线程同时对变量a访问或修改的情况.
      

  6.   

    楼主说的两个线程A,B对a访问或修改,只要不加对象锁或者方法锁的话都是可以的啊。不过有两个观点我还不太清楚:1.所谓“同时”修改,同时是指时刻上的精确相同?在我的理解上,两个线程应该是不能在同一时刻改变一个变量的值的,我这么理解是否正确? 2. 无论是哪个线程对a进行了修改,修改以后,另外一个线程再访问该变量的时候得到的应该是修改过的a的值,这是我对线程“知道”a的值被修改的理解,我这样理解是否符合楼主的原义?
      

  7.   

    楼主是想让一个线程修改a,一个线程读取a,然后修改a那个线程不会影响到读取a的线程读取到的值?
      

  8.   

    如果是这样的话那我还真不知道怎么实现,不过我想,如果变量或对象a在内存中只有一个副本的话,那A线程和B线程都是访问的都是这个副本,这样A对a所做的修改,B线程读a必然是受影响的啊。
      

  9.   

    按照书上的解释,永远循环或者输出0的现象是由于编译后指令重排序造成的。在不使用同步的情况下,JAVA的内存模型允许编译器对指令重新排序,并且缓存变量在寄存器中。