为什么没有 'volatile' 的程序会像 'volatile' 一样工作?
Why the program without 'volatile' works as 'volatile'?
如下所示,该程序有一个共享变量 flag
而没有 volatile
:
public class T {
public static void main(String[] args) {
TT jump = new TT(() -> {
while (true) {
if (TT.flag) {
System.out.println("jump");
break;
}
}
});
jump.start();
new TT(() -> {
TT.flag = true; // P1
LocalDateTime t1 = LocalDateTime.now();
while (true) {
if (Duration.between(t1, LocalDateTime.now()).toMillis() > 100) {
break;
}
}
System.out.println("flag");
}).start();
}
static class TT extends Thread {
public static boolean flag = false;
public TT(Runnable o) {
super(o);
}
}
}
程序总是returns正常。所以我相信 P1
行,其中 flag
设置为 true
,在其他线程中更新了 flag
。
但是为什么呢? flag
不是易变的,为什么它的值会立即更新?永远!
But why? flag is not volatile, why its value was updated immediately? Always!
你简直太幸运了;或不幸,取决于你的观点。我在 Ideone 上试过这个,发现它超时而不是正常终止。
记住:无法观察到并发错误与没有并发错误不同。
当您可以根据规范证明没有错误时,您对代码最有把握。这并不意味着代码会正常工作。这只是意味着问题出在 JVM 实现中。
特别是,您无法证明此代码会正确运行,因为在第二个 flag
中写入 flag
之间没有 happens-before 关系线程,并在第一个线程中读取。添加 volatile
创建此保证,因为易失性写入发生在易失性读取之前。
这并不是说它永远不会在没有 volatile 的情况下工作,只是不能保证:JVM 只需要至少按照规范要求的频率刷新线程的缓存值,但可以更频繁地这样做,或者实际上根本不缓存值。
如下所示,该程序有一个共享变量 flag
而没有 volatile
:
public class T {
public static void main(String[] args) {
TT jump = new TT(() -> {
while (true) {
if (TT.flag) {
System.out.println("jump");
break;
}
}
});
jump.start();
new TT(() -> {
TT.flag = true; // P1
LocalDateTime t1 = LocalDateTime.now();
while (true) {
if (Duration.between(t1, LocalDateTime.now()).toMillis() > 100) {
break;
}
}
System.out.println("flag");
}).start();
}
static class TT extends Thread {
public static boolean flag = false;
public TT(Runnable o) {
super(o);
}
}
}
程序总是returns正常。所以我相信 P1
行,其中 flag
设置为 true
,在其他线程中更新了 flag
。
但是为什么呢? flag
不是易变的,为什么它的值会立即更新?永远!
But why? flag is not volatile, why its value was updated immediately? Always!
你简直太幸运了;或不幸,取决于你的观点。我在 Ideone 上试过这个,发现它超时而不是正常终止。
记住:无法观察到并发错误与没有并发错误不同。
当您可以根据规范证明没有错误时,您对代码最有把握。这并不意味着代码会正常工作。这只是意味着问题出在 JVM 实现中。
特别是,您无法证明此代码会正确运行,因为在第二个 flag
中写入 flag
之间没有 happens-before 关系线程,并在第一个线程中读取。添加 volatile
创建此保证,因为易失性写入发生在易失性读取之前。
这并不是说它永远不会在没有 volatile 的情况下工作,只是不能保证:JVM 只需要至少按照规范要求的频率刷新线程的缓存值,但可以更频繁地这样做,或者实际上根本不缓存值。