使用同步时,JVM如何保证在引用对象中成员变量修改的可见性?

时间:2019-04-18 14:32:41

标签: java jvm

我想知道当使用同步时,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如何实现?

2 个答案:

答案 0 :(得分:3)

使用Memory Barriers/Fences强制线程之间的可见性。对于synchronized块,JVM将在块执行完成后插入一个内存屏障。

JVM通过CPU指令来实现内存屏障,例如在x86上,存储屏障是通过sfence完成的,而负载屏障是通过lfence指令完成的。还有mfence以及其他可能特定于CPU体系结构的指令。

答案 1 :(得分:1)

对于您的示例(仍不完整!),如果我们可以假设以下条件:

  1. 确保初始化线程test的线程A中的代码在线程B使用它之前运行。
  2. locker变量包含对线程A和B的同一对象的引用。

然后我们可以证明a == 2在您指定的时间点是正确的。如果不能保证前提条件1,则线程B可能会获得NPE。如果不能保证前提条件2(即线程A和B 可以在不同的对象上同步),那么就没有适当的先发生后关系来确保线程B看到线程A对{{ 1}}。

(@ NathanHughes评论说a是不必要的。我不一定同意。这取决于您的示例详细信息,但您还没有向我们展示。)


  

JVM如何实现它?

实际的实现是Java平台和(理论上)版本专用的。 JVM规范“内存模型”对遵守“规则”的程序的行为方式设置了约束。这完全取决于实现方式。

  

我知道内存屏障,它会将所有缓存刷新到主存储器吗?

那也是特定于实现的。有不同种类的内存屏障以不同的方式起作用。 JIT编译器将发出使用适当指令来满足JLS要求的本机代码。如果有一种方法可以无需进行完全的缓存刷新,则实现可以做到这一点。

(有一个JVM命令行选项告诉JIT编译器输出本机代码。如果您真的想知道幕后发生的事情,那么这是一个开始寻找的好地方。)

但是,如果您试图了解/分析应用程序的线程安全性,那么您应该按照Java内存模型来进行操作。另外,请使用更高级别的并发抽象,从而避免出现更低级别的陷阱。