我正在研究创建一个具有 final 字段的不可变数据类型(包括在分配给 final 成员字段之前构造和填充的数组),并注意到 JVM 似乎被指定为保证任何其他获取此对象引用的线程将看到初始化的字段和数组值(假设在构造函数中没有发布指向 this
的指针,请参阅 What is an "incompletely constructed object"? 和 How do JVM's implicit memory barriers behave when chaining constructors?)。
我很好奇这是如何在不同步对该对象的每次访问或以其他方式付出一些显着的性能损失的情况下实现的。根据我的理解,JVM 可以通过以下方式实现这一点:
- 在构造函数的末尾发出一个 write-fence
- 仅在写栅栏之后发布对新对象的引用
- 每当您引用对象的最终字段时发出读栅栏
我想不出更简单或更便宜的方法来消除其他线程看到未初始化的最终字段(或通过最终字段的递归引用)的风险。
这似乎会造成严重的性能损失,因为其他线程中的所有读栅栏都在读取对象,但是消除读栅栏会引入在对象引用发出之前在另一个处理器中看到它的可能性读栅栏或以其他方式查看与新初始化的最终字段对应的内存位置的更新。
有人知道这是怎么回事吗?这是否会带来显着的性能损失?
最佳答案
请参阅 this writeup 中的“内存屏障”部分.
StoreStore 屏障是在设置最终字段之后以及在将对象引用分配给另一个变量之前所必需的。这是您要询问的关键信息。
根据那里的“重新排序”部分,相对于对包含最终字段的对象的引用的存储,最终字段的存储不能重新排序。
此外,它声明在 v.afield = 1; x.finalField = v; ...; sharedRef = x;
,前两个都不能相对于第三个重新排序;这确保在存储对包含最终字段的对象的引用之前,存储为最终字段的对象的字段本身保证对其他线程可见。
总而言之,这意味着在存储对包含该字段的对象的引用之前,对最终字段的所有存储必须对所有线程可见。
关于java - final 字段如何防止其他线程看到部分构造的对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18116361/