我正在重读“实践中的Java并发”,我不确定我是否完全理解有关不可变性和安全发布的章节。
这本书的内容是:
任何线程都可以安全地使用不可变对象而无需额外的 同步,即使不使用同步发布 它们。
我不明白的是,为什么有人(有兴趣使他的代码正确)不安全地发布一些引用?
如果对象是不可变的,并且它发布得不安全,我理解获取对象引用的任何其他线程都会看到其正确的状态,因为正确的不变性提供了保证(使用final
字段等)。
但是如果发布不安全,另一个线程可能仍然会在发布后看到null
或前一个引用,而不是对不可变对象的引用,这对我来说似乎是没有人愿意的。< / p>
如果使用安全发布来确保所有线程都能看到新引用,那么即使该对象只是有效不可变(没有final
字段,但没办法把它们静音),然后一切都安全了。正如书中所说:
安全发布有效不可变的对象可以安全使用 没有额外同步的任何线程。
那么,为什么不变性(与有效不变性相比)如此重要?在什么情况下需要不安全的出版物?
答案 0 :(得分:9)
最好设计不需要同步的对象,原因有两个:
由于上述原因非常重要,因此最好学习有时很难的规则,作为编写者,制作不需要同步的安全对象,而不是希望代码的所有用户都记得正确使用它。
还要记住,作者并没有说该对象是不安全发布的,它是在没有同步的情况下安全发布的。
关于你的第二个问题,我刚刚检查过,本书并没有向你承诺另一个线程将始终看到对更新对象的引用,只要它确实如此,它将看到一个完整的对象。但我可以想象,如果它是通过另一个(Runnable
?)对象的构造函数发布的,那将是很好的。这确实有助于解释所有案例。
编辑:
有效不可变且不可变 有效不可变和不可变之间的区别在于,在第一种情况下,您仍然需要以安全的方式发布对象。对于真正不可变的对象,这不是必需的。所以真正的不可变对象是首选,因为它们更容易发布,原因如上所述。
答案 1 :(得分:5)
那么,为什么不变性(与有效不变性相比)如此重要?
我认为重点是真正的不可变对象以后更难破解。如果你宣布了一个字段final
,那么它就是最终的句号。您必须删除final
才能更改该字段,并且应该响起警报。但是如果你最初离开final
,有人可能会不小心只是添加一些改变字段的代码,并且繁荣 - 你被搞砸了 - 只有一些添加< / em>代码(可能在子类中),不对现有代码进行修改。
我还假设显式不变性使(JIT)编译器能够进行一些优化,否则这些优化很难或无法证明。例如,当使用volatile
字段时,运行时必须保证与写入和读取线程之间的先发生关系。在实践中,这可能需要内存屏障,禁用无序执行优化等 - 即性能损失。但是如果对象是(深度)不可变的(仅包含final
对其他不可变对象的引用),则可以放松要求而不会破坏任何东西:只需要编写和读取单个关系,就可以保证之前发生的关系引用,而不是整个对象图。
因此,显式不变性使程序更简单,以便人们更容易推理和维护和,使计算机更容易执行。随着对象图的增长,这些好处呈指数级增长,即对象包含包含对象的对象 - 如果一切都是不可变的,那么这一切都很简单。当需要可变性时,将其本地化到严格定义的位置并保持其他一切不可变仍然会带来很多好处。
答案 2 :(得分:2)
完成阅读第1-3章时,我和原始海报完全相同。我认为作者本可以更好地详细阐述这一点。
我认为其中的区别在于,当有效不可变对象的内部状态未被安全发布时,可以观察到它们处于不一致状态,而永远不会观察到不可变对象的内部状态处于不一致状态。
但是我确实认为如果没有安全地发布引用,可以观察到对不可变对象的引用是过时的/陈旧的。
答案 3 :(得分:2)
“不安全发布”通常适用于其他线程看到写入字段的最新值的情况,但让线程看到较早的值会相对无害。一个主要的例子是String
的缓存哈希值。第一次在hashCode()
上调用String
时,它会计算一个值并对其进行缓存。如果在同一个字符串上调用hashCode()
的另一个线程可以看到第一个线程计算的值,则不必重新计算哈希值(从而节省时间),但如果第二个线程没有,则不会发生任何不良看不到哈希值。它最终将执行一个本来可以避免的冗余但无害的计算。让hashCode()
安全地发布哈希值是可能的,但偶尔的冗余哈希计算比安全发布所需的同步更便宜 。实际上,除了相当长的字符串之外,同步成本可能会抵消缓存带来的任何好处。
不幸的是,我不认为Java的创建者会想象代码会写入某个字段的情况,并且更喜欢它应该对其他线程可见,但如果不是,则不要太在意,并且存储引用的位置然后,该字段将识别具有类似字段的另一个对象。这导致编写语义正确的代码的情况要麻烦得多,并且可能比可能工作的代码慢,但其语义不能得到保证。在某些情况下,除了使用一些无偿的final
字段以确保事情得到适当的“发布”之外,我不知道有什么好的补救措施。