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

问题描述 投票:1回答:2

我想知道在使用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是如何实现的?

java jvm
2个回答
2
投票

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

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

然后我们可以证明a == 2在你指出的点上是真的。如果不保证前提条件1,则线程B可以获得NPE。如果不保证前置条件2(即线程A和B可以在不同的对象上同步),那么就没有一个正确的先发生关系来确保线程B看到线程A对a的动作的结果。

(@NathanHughes评论说volatile是不必要的。我不一定同意。这取决于你的例子的细节,你仍然没有告诉我们。)


JVM如何实现它?

实际的实现是Java平台和(理论上)版本特定的。 JVM规范内存模型对遵守“规则”的程序的行为方式施加了限制。具体实现具体实现方式。

我知道内存障碍,它是否将所有缓存刷新到主存储器?

这也是具体的实施。有不同种类的记忆障碍以不同的方式起作用。 JIT编译器将发出使用适当指令的本机代码,以满足JLS所需的保证。如果有一种方法可以在不进行完全缓存刷新的情况下执行此操作,那么实现可能会这样做。

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

但是,如果您正在尝试理解/分析应用程序的线程安全性,那么您应该根据Java内存模型进行操作。此外,使用更高级别的并发抽象,可以避免较低级别的陷阱。


3
投票

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

JVM使用CPU指令实现内存屏障,例如:使用sfence完成商店屏障,并在x86上使用lfence指令完成加载屏障。还有mfence以及可能特定于CPU架构的其他指令。

© www.soinside.com 2019 - 2024. All rights reserved.