【问题标题】:Why is reference assignment atomic in Java?为什么Java中的引用赋值是原子的?
【发布时间】:2017-11-01 04:13:56
【问题描述】:

据我所知,引用分配在 64 位 JVM 中是原子的。 现在,我假设 jvm 在内部不使用原子指针来对此进行建模,因为否则就不需要原子引用。所以我的问题是:

java/Scala 的“规范”中的原子引用分配是保证会发生,还是大多数时候都是这样的巧合?

对于编译为 JVM 字节码的任何语言(例如 clojure、Groovy、JRuby、JPython...等)是否隐含原子引用分配?

如何在内部不使用原子指针的情况下,引用赋值是原子的?

【问题讨论】:

  • @JornVernee 我的意思是指派不交换,谢谢指正
  • 这可能会给出第一个线索:stackoverflow.com/questions/23232242/…
  • 我做了...但是我一直在互联网上搜索 getfield、putfiled 和 iastore 是如何实现的,但我没有找到不涉及挖掘 openjvm 代码的解释, 我不够熟练:(

标签: java scala concurrency jvm atomic


【解决方案1】:

原子参考分配在规范中。

对引用的写入和读取始终是原子的,无论 它们是作为 32 位还是 64 位值实现的。

引自 JSR-133:Java(TM) 内存模型和线程规范,第 12 节 doublelong 的非原子处理,@987654321 @。

【讨论】:

  • 好的,这回答了第一部分,但没有回答其他 2。老实说,我在这里最好奇的是,如果引用不是原子指针,那么这种行为是如何发生的,这与如何一个人会实现一个 JVM,而不是 Java 和/或 jvm 的规范。
  • 是的。在我不确定的事情上,我故意不假装是个聪明人。不过,按照我的理解,这是 JVM 的规范,所以我希望它涵盖在 JVM 上运行的所有语言。而我阅读它的方式,需要内部指针操作以原子方式发生(至少从 JVM 外部看到)。
  • 嗯,是的,但我怀疑这些是实际的原子指针操作,否则比较和交换之类的操作也将是使用标准引用的原子操作。
  • 不错的答案 - 很好的发现;我对此投了赞成票。并感谢您省略了为我的回答留出空间的那一小部分;-)
  • 链接的 PDF 描述了 JSR133 提出的 Java 5 的内存模型。为了证明它确实进入了 Java 5 JLS 并且今天仍然存在,最好链接到 JLS §17.7
【解决方案2】:

正如other answer 所述,Java 内存模型声明引用读/写是原子的。

当然,那是 Java 语言 内存模型。另一方面:无论我们谈论 Java、Scala、Kotlin 还是……最终,一切都会被编译成 字节码

Java 没有特殊的字节码指令。 Scala 最终使用了完全相同的指令。

导致:该内存模型的属性必须在虚拟机平台内部实现。因此,它们必须也适用于平台上运行的其他语言。

【讨论】:

  • 由于名称是“JavaTM 内存模型”,我不确定它是否指的是整个 JVM,因此我认为可能存在可用于其他 JVM 语言的非原子分配指令
  • 当时写那个内存模型的时候,Java VM平台上只有Java语言。我想。
  • 即使是现在,我也不认为有 Java 本身不使用的 JVM 指令。
  • @Jasper-M:有。例如,swap 从未被javac 生成的代码使用。在 Java 7 中,invokedynamic 指令并没有被 Java 本身使用,这是那个 JVM 版本的一个很大的特点……
  • 在得出这个结论时要记住的一点是,这仅适用于其他语言的概念(即引用)被 1:1 编译为等效字节码工件的情况。例如。特定语言的引用可能与 Java 的对象引用的作用相距甚远,以至于它不匹配唯一的字节码引用变量,但是,例如复合数据结构。对于 Java,不可能遇到具有尚未初始化的 vtable 的对象引用,但这不一定适用于具有完全不同的方法分派算法的另一种语言。
【解决方案3】:

首先,引用分配是原子的,因为规范是这么说的。除此之外,JVM 实现者实现这一约束没有任何障碍,因为 64 位引用通常仅用于 64 位架构,其中原子 64 位分配是免费的。

您的主要困惑源于假设附加的“原子引用”功能正是由于其名称而意味着的。但是AtomicReference 类提供了更多功能,因为它封装了volatile 引用,在多线程执行中具有更强的内存可见性保证。

进行原子引用更新并不一定意味着读取引用的线程也会看到关于可通过该引用访问的对象的字段的一致值。它只保证您将读取null 引用或对实际由某个线程存储的现有对象的有效引用。如果您想要更多保证,则需要同步、volatile 引用或AtomicReference 等构造。

AtomicReference 还提供 原子更新 操作,例如 compareAndSetgetAndSet。这些对于使用内置语言结构的普通引用变量是不可能的(但仅限于像 AtomicReferenceFieldUpdaterVarHandle 这样的特殊类)。

【讨论】:

  • @George:设置AtomicReference 可以保证在通过AtomicReference 发布它之前对该对象所做的所有更新的可见性,而不是允许您进行更新之后。通过普通引用变量发布对象不会对更新做出任何保证,无论您是在此之前还是之后进行更新。它甚至不保证其他线程会看到新的引用。
  • AtomicReference 的意义上,“原子指针”不一定是“原子引用”。因此,有必要指出引用(即底层指针) 是原子的,仍然不匹配 AtomicReference 的功能。
  • “发布”意味着分配给其他线程可见的变量。不,通过正常参考发布时,不保证以前更新的可见性。如果你写point=new Point(42,100) 并且point 既不是final 也不是volatile,其他线程可能看到新的引用,同时仍然看到默认值(0),@ 987654340@ 或 y ,或两者兼有(在没有其他同步的情况下)。
  • 这与旧实例无关;分配之前的引用可能是null,这没关系。问题是,new Point(42,100) 不是原子操作。它创建一个新的Point 实例,所有字段都为其默认值(0int),然后将执行构造函数,它将值42 分配给x100y,然后将引用分配给point从单个线程的角度来看。不同的线程可能会看到这些操作乱序,请参阅对新 Point 实例的引用,而不会看到分配的效果。
  • 这不适用于不可变对象。如果您将xy 声明为final,它们就不会受到此类数据竞争的影响。或者,您可以将point 声明为final,但这意味着整个操作是构造函数或初始化程序的一部分。或者,好吧,将point 声明为volatile 可确保其他线程在通过point 参考看到新的Point 实例时看到所有以前的更新(x=42; y=100;)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-12
  • 2016-03-14
  • 2019-05-05
相关资源
最近更新 更多