我想知道在使用synchronized时,JVM如何保证引用对象中成员变量修改的可见性。
我知道synchronized和volatile将为变量修改提供可见性。
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?
线程首先调用sychronized
块中的调用,然后将对象传递给线程B(将引用写入volatile
变量。)。然后线程B调用再次修改(在synchronized
中)。
是否有保证== 2? JVM是如何实现的?
对于您的(仍然不完整!)示例,如果我们可以假设以下内容:
test
中的代码保证在线程B使用它之前运行。locker
变量包含对线程A和B的相同对象的引用。然后我们可以证明a == 2
在你指出的点上是真的。如果不保证前提条件1,则线程B可以获得NPE。如果不保证前置条件2(即线程A和B可以在不同的对象上同步),那么就没有一个正确的先发生关系来确保线程B看到线程A对a
的动作的结果。
(@NathanHughes评论说volatile
是不必要的。我不一定同意。这取决于你的例子的细节,你仍然没有告诉我们。)
JVM如何实现它?
实际的实现是Java平台和(理论上)版本特定的。 JVM规范内存模型对遵守“规则”的程序的行为方式施加了限制。具体实现具体实现方式。
我知道内存障碍,它是否将所有缓存刷新到主存储器?
这也是具体的实施。有不同种类的记忆障碍以不同的方式起作用。 JIT编译器将发出使用适当指令的本机代码,以满足JLS所需的保证。如果有一种方法可以在不进行完全缓存刷新的情况下执行此操作,那么实现可能会这样做。
(有一个JVM命令行选项告诉JIT编译器输出本机代码。如果你真的想知道底层发生了什么,那么这是一个开始寻找的好地方。)
但是,如果您正在尝试理解/分析应用程序的线程安全性,那么您应该根据Java内存模型进行操作。此外,使用更高级别的并发抽象,可以避免较低级别的陷阱。
使用Memory Barriers/Fences强制执行线程之间的可见性。在synchronized
块的情况下,JVM将在块执行完成后插入内存屏障。
JVM使用CPU指令实现内存屏障,例如:使用sfence
完成商店屏障,并在x86上使用lfence
指令完成加载屏障。还有mfence
以及可能特定于CPU架构的其他指令。