关于volatile关键字的一个问题
/ java经测试,打开注释行,子线程就不会陷入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,编译器就不能再做你楼上说的那个优化了。