我最近开始学习C#,直接学习内存模型。 C# 和 Java 对于 volatile 字段的读取和写入具有类似(尽管可能不相同)的线程安全保证。但与 Java 中的 final
字段写入不同,C# 中的 readonly
字段写入不提供任何特定的线程安全保证。思考 C# 中线程安全的工作原理让我怀疑 final
字段的行为方式与 Java 中的方式是否有任何真正的优势。
三年前我了解到 final
的重要性。我问this question并得到了我接受的详细答案。但现在我认为这是错误的,或者至少是无关紧要的。我仍然认为字段应该尽可能是final
,只是不是出于普遍认为的原因。
构造函数返回后,final
字段的值保证对任何其他线程可见。但对对象本身的引用必须以线程安全的方式发布。 如果引用安全发布,那么final
的可见性保证就变得多余。
我考虑了它可能与公共(public)静态
字段有关。但从逻辑上讲,类加载器必须同步类的初始化。同步使得final
的线程安全变得多余。
所以我提出了一个异端的想法,即final
的唯一真正值(value)是使不变性 self 记录和 self 执行。实际上,私有(private)非最终字段(尤其是数组元素)只要在构造函数返回后不被修改,就是完全线程安全的。
我错了吗?
编辑:释义《Java 并发实践》第 3.5 节,
Two things can go wrong with improperly published objects. Other threads could see a stale value for the reference, and thus see a null reference or other older value even though a value has been set. But far worse, other threads could see an up-to-date value for the reference, but stale values for the state of the object.
我了解 final
字段如何解决第二个问题,但不了解第一个问题。迄今为止得票最高的答案认为第一个问题不是问题。
编辑2:这个问题是由于术语困惑而产生的。
就像a similar question的提问者一样,我一直将术语“安全发布”理解为对象的内部状态和对对象本身的引用保证对其他线程可见。为了支持这个定义,Effective Java 引用了 Goetz06, 3.5.3 将“安全发布”定义为(强调)
Transferring such an object reference from one thread to others
也支持这个定义,请注意上面解释的《Java 并发实践》部分将可能过时的引用称为“不正确发布”。
无论你怎么调用它,我认为不安全地发布对不可变对象(immutable对象)的引用可能会有用。但根据this answer , 它可以。 (给出的示例有一个原始值,但相同的原则也适用于引用值。)
最佳答案
But the reference to the object itself must be published in a thread safe manner. If the reference is published safely, then the visibility guarantee of
final
becomes redundant.
第一句话是错误的;因此,第二个是无关紧要的。在存在其他安全发布技术(例如同步或 volatile )的情况下,final
可能是多余的。但不可变对象(immutable对象)的要点是它们本质上是线程安全的,这意味着无论引用如何发布,它们都将处于一致的状态。因此,您首先不需要这些其他技术,至少就安全发布而言是这样。
编辑:OP正确地指出“安全发布”一词存在一些含糊之处。在这种情况下,我指的是对象内部状态的一致性。在我看来,影响引用的可见性问题是一个有效但单独的问题。
关于Java Final 字段异端 : am I reasoning correctly?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/45928932/