sto*_*r96 16 java multithreading volatile java-memory-model happens-before
以下代码示例显示了一种常见方法,用于演示由于缺少发生之前关系而导致的并发问题。
private static /*volatile*/ boolean running = true;
public static void main(String[] args) throws InterruptedException {
new Thread() {
@Override
public void run() {
while (running) {
// Do nothing
}
}
}.start();
Thread.sleep(1000);
running = false;
}
Run Code Online (Sandbox Code Playgroud)
如果running是volatile,程序保证在大约一秒后终止。但是,如果running不是volatile,则根本无法保证程序终止(因为在running这种情况下没有发生之前关系或保证变量更改的可见性),而这正是我的测试中发生的情况。
根据JLS 17.4.5,人们还可以通过写入和读取另一个volatile变量来强制执行发生前关系running2,如以下代码示例所示。
private static boolean running = true;
private static volatile boolean running2 = true;
public static void main(String[] args) throws InterruptedException {
new Thread() {
@Override
public void run() {
while (running2 || running) {
// Do nothing
}
}
}.start();
Thread.sleep(1000);
running = false;
running2 = false;
}
Run Code Online (Sandbox Code Playgroud)
该volatile变量在每次循环迭代中被读取,并且当大约一秒后被running2读取时,由于发生前关系,还保证该变量被随后读取。因此,程序保证在大约一秒后终止,这正是我的测试中发生的情况。falserunningfalse
但是,当我将变量的读取放入循环内的running2空语句中时(如以下代码示例所示),程序不会在我的测试中终止。ifwhile
private static boolean running = true;
private static volatile boolean running2 = true;
public static void main(String[] args) throws InterruptedException {
new Thread() {
@Override
public void run() {
while (running) {
if (running2) {
// Do nothing
}
}
}
}.start();
Thread.sleep(1000);
running = false;
running2 = false;
}
Run Code Online (Sandbox Code Playgroud)
这里的想法是,volatile读取running2就像编译器内存屏障:编译器必须使汇编重新读取非volatile变量,因为读取running2可能与另一个线程中的释放操作同步。这将保证非易失性变量(如running.
但我的 JVM 似乎没有这样做。 这是编译器或 JVM 错误,还是 JLS 是否允许volatile在不需要值时删除读取的优化?(它仅控制一个空if主体,因此程序行为不依赖于正在读取的值,仅依赖于创建发生之前的关系。)
我认为 JLS 适用于源代码,并且由于running2is volatile,因此不应允许由于优化而删除读取变量的效果。这是编译器或 JVM 错误,还是存在实际上允许此类优化的规范?
小智 1
\n\n这是 JVM 错误,还是 JLS 允许它在不需要值时删除易失性读取?
\n
两者都不是。
\n根据 JLS,此执行是有效的。
\n第二个线程必须在读取后不久完成running2 == true。
\n但是 JLS 不保证一个线程中的写入在另一个线程中变得可见所需的时间。
\n因此,您的程序执行是有效的,因为它对应于写入running2 = false需要很长时间才能传播到另一个线程的情况。
顺便说一句,在我的 java 版本(OpenJDK 64 位服务器 VM(内部版本 17.0.3+7-suse-1.4-x8664,混合模式))上,程序在大约 1 秒内完成。\n这也是有效的执行 \xe2\x80\x94 这对应于写入更快地传播到第二个线程的
情况。running2 = false
PS你提到“记忆障碍”。
\n对于内存屏障,通常存在一些最大时间,之后保证传播到其他线程。
\n但是 JLS 不按照内存屏障进行操作,也不必使用它们,并且实际上仅保证这一点:
\n\n实现可以自由地生成它喜欢的任何代码,只要程序的所有结果执行都会生成可以由内存模型预测的结果。
\n
PSS 如果您想查看 JVM 为您的程序生成的真实汇编代码,您可以使用+PrintAssembly。
\n| 归档时间: |
|
| 查看次数: |
555 次 |
| 最近记录: |