您引用的文章在概念上是正确的。正如您的问题一样,它的术语和用法有些不准确,这可能导致潜在的误解和误解。似乎我在这里使用术语,但是Java内存模型非常微妙,如果术语不够精确,那么一个人的理解就会受到影响。
我将从您的问题(和评论)中摘录要点,并提供答复。
可以根据构造函数内部的指令对构造函数返回的值的分配进行重新排序。
几乎可以,不是指令,而是可以重新排序的 内存 *** 作
(读取和写入)。线程可以按特定顺序执行两条写指令,但是数据到达内存的方式以及这些写给其他线程的可见性可能以不同的顺序发生。
我认为从线程执行的角度来看,可以保证
MyInt a = new MyInt(42)的分配与的分配x具有先发生后关系a。
再次,差不多。诚然,按 程序顺序 是,对的赋值
x发生在对的赋值之前
a。但是, before-before
是适用于所有线程的全局属性,因此就特定线程而言,谈论before-before是没有意义的。
但是,这两个值都可能缓存在寄存器中,并且可能未按照最初写入的顺序将它们刷新到主存储器。没有内存屏障,因此另一个线程可以在写入x的值之前读取a的值。
再一次,差不多。值可以缓存在寄存器中,但是部分内存硬件(例如缓存或写缓冲区)也可能导致重新排序。硬件可以使用多种机制来更改顺序,例如缓存刷新或内存屏障(通常不会引起刷新,而只是防止某些重新排序)。但是,从硬件角度考虑这一问题的困难在于,实际系统非常复杂并且具有不同的行为。例如,大多数CPU具有几种不同的存储屏障类型。如果要考虑JMM,则应该考虑模型的要素:通过建立事前发生的关系来限制重新排序的内存 *** 作和同步。
因此,为了从JMM的角度重新审视此示例,我们看到对字段
x的写入和
a对程序顺序的字段写入。在此程序中,没有什么限制重新排序,即没有同步,没有对volatile的 *** 作,没有对最终字段的写入。这些写 *** 作之间没有事前发生的关系,因此可以对它们进行重新排序。
有几种方法可以防止这些重新排序。
一种方法是进入
x决赛。之所以可行,是因为JMM表示在构造函数返回 之前发生 写入最终字段之前-在构造函数返回之后 发生的 *** 作 之前
。由于
a是在构造函数返回之后写入的,因此final字段的初始化
x发生在写入之前
a,并且不允许重新排序。
另一种方法是使用同步。假设
MyInt实例在另一个类中使用,如下所示:
class OtherObj { MyInt a; synchronized void set() { a = new MyInt(42); } synchronized int get() { return (a != null) ? a.getValue() : -1; }}
set()在写入
x和
a字段之后,将在调用结束时进行解锁。如果另一个线程调用
get(),它将在调用开始时锁定。这将在锁的释放(末尾)
set()和锁的获取(开始时)之间建立事前关联关系
get()。这意味着在调用开始之后,对写入的写入
x和
a无法重新排序
get()。因此,读取器线程将看到两者的有效值
a,
x并且永远不会找到非null
a和未初始化的值
x。
当然,如果读取器线程
get()较早调用,则它可能看似
a为null,但是这里没有内存模型问题。
您
Foo和的
Bar例子很有趣,您的评估基本上是正确的。在分配给最终数组字段之前发生的对数组元素的写入之后将无法重新排序。在分配给最终数组字段之后发生的对数组元素的写 *** 作可能会相对于稍后发生的其他内存 *** 作进行重新排序,因此其他线程的确可能会看到过时的值。
在评论中,您曾问过这是否有问题,
String因为它具有包含其字符的最终字段数组。是的,这是一个问题,但是如果您查看String.java构造函数,则它们都非常小心,要在构造函数的最后将赋值给final字段。这样可以确保适当地可见数组的内容。
是的,这很微妙。:-)但是,只有当您尝试变得聪明时才真正出现问题,例如尝试避免使用同步或易变变量。大多数时候这样做是不值得的。如果您坚持“安全发布”的做法,包括
this在构造函数调用期间不泄漏,以及使用同步存储对构造对象的引用(
OtherObj例如上面的示例),那么事情将完全按照您的预期进行。
参考文献:
- Goetz,《 Java并发实践》 ,第3章, 共享对象 。这包括对内存可见性和安全发布的讨论。
- Manson / Goetz, Java内存模型常见问题解答 。http://www.cs.umd.edu/~pugh/java/memoryModel/jsr-133-faq.html。有点旧,但有一些很好的例子。
- Shipilev,Java内存模型实用程序。http://shipilev.net/blog/2014/jmm-pragmatics/。一位Oracle性能专家的演讲幻灯片和谈话记录。关于JMM的知识比您想了解的更多,还有一些指向Java未来版本中潜在的JMM修订版的指针。
- OpenJDK 8 String.java源代码。http://hg.openjdk.java.net/jdk8/jdk8/jdk/file/tip/src/share/classes/java/lang/String.java
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)