经测试,打开注释行,子线程就不会陷入while死循环了,为什么呢

public class VolatileTest3   {    
// b使用volatile修饰
    public static volatile long b = 0;    
    //消除缓存行的影响
    public static long a1,a2,a3,a4,a5,a6,a7,a8;    
    // c不使用volatile修饰
    public static long c = 0;    
    public static void main(String[] args) 
    throws InterruptedException {        
    new Thread(()->{            
    while (c == 0) {                
    //long x = b;
            }
            System.out.println("c=" + c);
        }).start();

        Thread.sleep(100);

        b = 1;
        c = 1;
    }
}

这样解释:

如果不加volatile,java编程语言的java memory model允许一个线程读到另一个线程任何一次写进去的值(可以是初值0也可以是主线程写入的1),只要不是happens-after它的就可以。但这个程序两个线程没有任何同步,所以没有任何happens-before关系。所以,就算主线程写,另一个线程永远读到c == 0,也是允许的。只要允许,你看到的“程序永远退不出去”就是合理的结果。至于为什么会出现这种现象,你暂且认为是巧合吧,反正这是《Java语言标准》允许的,JVM没做错什么。

但是一旦加上volatile,所有线程对c的读写操作就构成一个序列。因为main早晚会执行完,所以早晚会又一个对c的写操作,写入1。由于new thread会不断读c,早晚会有一次读happens after那个往c里写1的操作。对于volatile变量来说,写之后的读都能看到那个写的值“1”。所以那个new thread早晚可以看到c == 1。

关键词:“happens-before”。
参考资料:《java language specification》里的java memory model

这个话没有错,但是这个话感觉不痛不痒。。。不知道是不是jvm再这块实现做了一些特殊处理,比如更新volatile的修饰的变量的时候,顺带把局部变量表里面的所有变量都更新了一此


理论上,就算注释去掉,多线程下这个程序应该也不是线程安全的。在空循环时,由于循环体执行过快(空代码),结果就是大家都知道的由于缓存问题导致的非安全性。非空循环中,jvm有可能认为循环体代码执行时间远大于刷新一次缓存的时间,所以有可能会去主存读一下真实值,但并不保证,所以为写出正确的程序还是要加上volatile关键字的


不能这么解释。(1)问题不是缓存引起的。根本原因是多个线程没有任何同步,导致读写的顺序不能保证。java memory model为了适应这种硬件上的读写不保证顺序的现象,允许永远看到旧值。这是根因。然后优化编译器又利用了这种许可,把while的条件优化成true了。(2)java1.5开始就没有“主内存”的概念了。“主内存”是java1.0的概念,而且没什么鸟用。


问一下暖神,下面两段代码
[code]


public class Main2 {

    private static int a = 0;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            while (a == 0) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("end");
        }).start();
        Thread.sleep(1000);
        a = 1;
    }

}
[/code]

[code]
public class Main2 {

    private static int a = 0;

    public static void main(String[] args) throws InterruptedException {

        new Thread(() -> {
            while (a == 0) {
//                try {
//                    Thread.sleep(1000);
//                } catch (InterruptedException e) {
//                    e.printStackTrace();
//                }
            }
            System.out.println("end");
        }).start();
        Thread.sleep(1000);
        a = 1;
    }

}

[/code]

为什么第一段会退出循环,第二段不会退出循环

你可以理解为Thread.sleep会访问当前线程的interrupted状态,而这个状态是volatile的。其实sleep和interrupt之间是有同步的,只是你没有调用interrupt而已。因为需要检查interrupted,编译器就不能再做你楼上说的那个优化了。