public class Test {
private static volatile boolean flag = false;
private static int i = 1;
public static void main(String[] args) {
new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
flag = true;
i += 1;
}).start();
new Thread(() -> {
while (!flag) {
if (i != 1) {
System.out.println(i);
}
}
System.out.println(flag);
System.out.println(i);
}).start();
}
}
变量 i
写在volatile变量标志之后,但代码输出true 2。看来 i
第一个线程对第二个线程可见。
根据我的理解,变量我应该先写flag,然后第二个线程才能知道变化。
3条答案
按热度按时间noj0wjuj1#
内存模型定义了保证,但是在保证之上可能发生任何事情。
在x86上,所有的写操作都有发布语义,一旦您写入一个变量,它的更新值将尽快从其他线程中可见。
因此,写入volatile变量之前的操作发生在读取volatile变量之后的操作之前,这一事实并不妨碍写入volatile变量之后的操作在读取volatile变量之后变得可见。
pu82cl6c2#
你的代码遭遇数据竞争。
数据争用是指对同一地址有两个内存操作,它们不是按“发生在”关系排序的,并且这些操作中至少有一个是写操作。
在这种情况下,写入
i
这就是问题所在。写信给
i
,是在写入volatile变量之后flag
因此,写作和写作之间不存在“先发生后发生”的关系i
和阅读i
.如果你愿意写
i
在你写信给flag
,关系之前会发生以下情况:写
i
发生在写之前flag
由于程序顺序规则写
flag
在读到之前发生flag
由于易失性变量规则(在硬件级别上,这是缓存一致性的任务)。阅读
flag
发生在i
由于程序顺序规则。因为before关系是传递的,所以
i
在读取if之前发生i
.就像你已经指出的,如果你移动
i
前面写着国旗;数据竞赛已经结束。s4chpxco3#
根据语言标准(§17.4):
字段可以声明为volatile,在这种情况下,java内存模型确保所有线程都能看到变量的一致值
因此,非正式地说,所有线程都可以看到该变量的must update值。
然而,volatile子句不仅意味着确保目标变量的可见性保证,而且还意味着完全的volatile可见性保证,即:
实际上,javavolatile的可见性保证超出了volatile变量本身。能见度保证如下:
如果线程a写入一个易失性变量,而线程b随后读取同一个易失性变量,那么线程a在写入该易失性变量之前可见的所有变量在线程b读取该易失性变量之后也将可见。
如果线程a读取一个易失性变量,那么在读取该易失性变量时线程a可见的所有变量也将从主内存中重新读取。
根据我的理解,变量我应该先写flag,然后第二个线程才能知道变化。
“在写入volatile变量之前线程a可以看到的所有变量”,它并不指对这些变量的操作。