我想知道当使用同步时,JVM如何保证所引用对象中成员变量修改的可见性。
我知道同步和易变将为变量修改提供可见性。
class Test{
public int a=0;
public void modify(){
a+=1;
}
}
//Example:
// Thread A:
volatile Test test=new Test();
synchronized(locker){
test.modify();
}
// then thread B:
synchronized(locker){
test.modify();
}
// Now, I think test.a==2 is true. Is it ok? How JVM implements it?
// I know the memory barrier, does it flush all cache to main storage?
线程A调用首先在sychronized
块中修改,然后将对象传递给线程B(将引用写入volatile
变量。)。
然后,线程B再次调用修改(在synchronized
中)。
是否可以保证a == 2? JVM如何实现?
答案 0 :(得分:3)
使用Memory Barriers/Fences强制线程之间的可见性。对于synchronized
块,JVM将在块执行完成后插入一个内存屏障。
JVM通过CPU指令来实现内存屏障,例如在x86上,存储屏障是通过sfence
完成的,而负载屏障是通过lfence
指令完成的。还有mfence
以及其他可能特定于CPU体系结构的指令。
答案 1 :(得分:1)
对于您的示例(仍不完整!),如果我们可以假设以下条件:
test
的线程A中的代码在线程B使用它之前运行。locker
变量包含对线程A和B的同一对象的引用。然后我们可以证明a == 2
在您指定的时间点是正确的。如果不能保证前提条件1,则线程B可能会获得NPE。如果不能保证前提条件2(即线程A和B 可以在不同的对象上同步),那么就没有适当的先发生后关系来确保线程B看到线程A对{{ 1}}。
(@ NathanHughes评论说a
是不必要的。我不一定同意。这取决于您的示例详细信息,但您还没有向我们展示。)
JVM如何实现它?
实际的实现是Java平台和(理论上)版本专用的。 JVM规范“内存模型”对遵守“规则”的程序的行为方式设置了约束。这完全取决于实现方式。
我知道内存屏障,它会将所有缓存刷新到主存储器吗?
那也是特定于实现的。有不同种类的内存屏障以不同的方式起作用。 JIT编译器将发出使用适当指令来满足JLS要求的本机代码。如果有一种方法可以无需进行完全的缓存刷新,则实现可以做到这一点。
(有一个JVM命令行选项告诉JIT编译器输出本机代码。如果您真的想知道幕后发生的事情,那么这是一个开始寻找的好地方。)
但是,如果您试图了解/分析应用程序的线程安全性,那么您应该按照Java内存模型来进行操作。另外,请使用更高级别的并发抽象,从而避免出现更低级别的陷阱。