安全发布和不可变与有效不可变的优势

我正在重新阅读Java Concurrency In Practice,我不确定我是否完全理解有关不可变性和安全发布的章节。

这本书的内容是:

任何线程都可以安全地使用不可变对象而无需额外的同步,即使不使用同步来发布它们也是如此。

我不明白的是,为什么有人(有兴趣使他的代码正确 )不安全地发布一些参考?

如果对象是不可变的,并且它是不安全地发布的,我理解获得对象引用的任何其他线程都会看到它的正确状态,因为正确的不变性提供了保证(使用final字段等)。

但是如果发布是不安全的,那么另一个线程可能仍然会在发布之后看到null或者先前的引用,而不是对不可变对象的引用,这在我看来就像没有人想要的那样。

如果使用安全发布来确保所有线程都能看到新引用,那么即使对象实际上是不可变的 (没有final字段,但也无法将它们静音),那么一切都是安全的。 正如书中所说:

安全发布的有效不可变对象可以被任何线程安全地使用而无需额外的同步。

那么,为什么不变性(与有效不变性相比)如此重要? 在什么情况下需要不安全的出版物?

希望设计不需要同步的对象有两个原因:

  1. 对象的用户可能忘记同步。
  2. 尽管开销非常小,但同步并不是免费的,特别是如果您的对象不经常使用并且由许多不同的线程使用。

由于上述原因非常重要,因此最好学习有时很难的规则,作为编写者,制作不需要同步的安全对象,而不是希望代码的所有用户都记得正确使用它。

还要记住,作者并没有说该对象是不安全发布的,它是在没有同步的情况下安全发布的。

关于你的第二个问题,我刚刚检查了一下,本书并没有向你承诺另一个线程将始终看到对更新对象的引用,只要它确实如此,它将看到一个完整的对象。 但我可以想象,如果它是通过另一个( Runnable ?)对象的构造函数发布的,那将是很好的。 这确实有助于解释所有案例。

编辑:

有效不可变和不可变有效不可变和不可变之间的区别在于,在第一种情况下,您仍然需要以安全的方式发布对象。 对于真正不可变的对象,这不是必需的。 因此,真正的不可变对象是首选,因为它们更容易发布,原因如上所述。

那么,为什么不变性(与有效不变性相比)如此重要?

我认为重点是真正的不可变对象以后更难破解。 如果你宣布了一场final ,那么它就是最后一段时间。 您必须删除final才能更改该字段,并且应该响起警报。 但是,如果你最初离开final ,有人可能会不小心添加一些改变字段的代码 ,并且繁荣 – 你被搞砸 – 只有一些添加的代码(可能在子类中),不修改现有代码。

我还假设显式不变性使(JIT)编译器能够进行一些优化,否则这些优化很难或无法certificate。 例如,在使用volatile字段时,运行时必须保证与写入和读取线程之间的先发生关系。 在实践中,这可能需要内存屏障,禁用无序执行优化等 – 即性能损失。 但是如果对象是(深度)不可变的(仅包含对其他不可变对象的final引用),则可以放宽要求而不会破坏任何东西:只有通过编写和读取单个引用才能保证发生之前的关系,而不是整个对象图。

因此,显式不变性使程序更简单,因此人们更容易推理和维护, 并且更容易使计算机以最佳方式执行。 随着对象图的增长,这些好处呈指数级增长,即对象包含包含对象的对象 – 如果一切都是不可变的,那么这一切都很简单。 当需要可变性时,将其本地化到严格定义的位置并保持其他一切不可变仍然会带来很多好处。

完成阅读第1-3章后,我和原始海报的问题完全相同。 我认为作者本可以更好地详细阐述这一点。

我认为不同之处在于,当有效不可变对象的内部状态未被安全发布时,可以观察到它们处于不一致状态,而永远不会观察到不可变对象的内部状态处于不一致状态。

但是我确实认为如果没有安全地发布引用,则可以观察到对不可变对象的引用是过时的/陈旧的。

“不安全的发布”通常适用于其他线程看到写入字段的最新值的情况,但让线程看到较早的值会相对无害。 一个主要的例子是String的缓存哈希值。 第一次在String上调用hashCode() ,它将计算一个值并对其进行缓存。 如果在同一个字符串上调用hashCode()另一个线程可以看到第一个线程计算的值,则不必重新计算哈希值(从而节省时间),但如果第二个线程没有,则不会发生任何不良情况看哈希值。 它只会最终执行一个本来可以避免的冗余但无害的计算。 让hashCode()安全地发布哈希值是可能的,但偶尔的冗余哈希计算要比安全发布所需的同步便宜得多。 实际上,除了相当长的字符串之外,同步成本可能会抵消缓存带来的任何好处。

遗憾的是,我不认为Java的创建者会想象代码会写入某个字段的情况,并希望它应该对其他线程可见,但如果不是,则不要太在意,并且将引用存储到字段中反过来会识别具有类似字段的另一个对象。 这导致写入语义正确的代码的情况要麻烦得多,并且可能比可能工作的代码慢,但其语义不能得到保证。 在某些情况下,除了使用一些无偿的final字段以确保事情得到适当的“发布”之外,我不知道有什么好的补救办法。