在此 class 的对象上调用 start() 安全吗? Java 并发实践中的一个例子

Is calling start() on a object of this class safe? An example from Java Concurrency in practice

首先,我将给出我将要讨论的源代码的链接,因为 copy/paste 会使这个问题页面太长。

在 JCIP 的清单 5.15 http://jcip.net/listings/CellularAutomata.java 中,我想象在某些主要方法中,将创建一个 CellularAutomata 对象,然后对该对象调用 start()。

但是,这样做可以吗?当对象的 start 方法被调用时,它将创建 N(处理器数量)个具有 Worker 实例的线程。似乎使用 worker 对象创建的 N 个线程可能会看到该 Worker 的不完整引用或对象。

其背后的原因是,this 引用在调用 CellularAutomata 对象的构造过程中转义 new Runnable() 和 new Worker(mainBoard.getSubBoard(count, i))

并且因为 Worker[] 个工人;和 CyclicBarrier 屏障;是 CellularAutomata 对象的字段,在该对象的 start() 方法中创建的线程可能无法看到处于正确状态的这些对象。

我认为这与 Holder 的示例相似 http://jcip.net/listings/StuffIntoPublic.java http://jcip.net/listings/Holder.java 其他线程可能看不到 Holder 的字段。 我知道 Holder 示例是有问题的,因为该字段不是最终的,因此可能不可见,而在 CellularAutomata 中它们是最终的。我读到只有 final 字段的 class 在发布时保证其字段的可见性。但是,我还读到,虽然最终字段可能是 class 的唯一字段,但如果 class 未正确构造,那么该保证就消失了。在这个例子中,由于 this 引用转义,我假设它没有正确构造。这是一个隐式让 this 引用转义的示例,这类似于 CellularAutomata 中发生的事情。 http://jcip.net/listings/ThisEscape.java

如果我的想法需要纠正,请告诉我,我将不胜感激。这段并发之旅让我充满了很多疑惑和问题,如果您有任何其他关于我可以在 Java 中学习并发的地方和并发基础的参考资料,请告诉我。

谢谢

您可以阅读 Java 语言规范的相关部分:17.5. final Field Semantics

第一个相关部分(重点是我加的):

An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an object after that object has been completely initialized is guaranteed to see the correctly initialized values for that object's final fields.

在构造函数完成之前,任何其他线程都看不到 this 引用,所以没关系。 构造函数中的 this 引用 "escaping" 没有什么神奇之处;相关的是没有其他线程应该看到它(在构造函数完成之前)。

JLS 的下一段对此进行了扩展(强调和斜体由我添加):

The usage model for final fields is a simple one: Set the final fields for an object in that object's constructor; and do not write a reference to the object being constructed in a place where another thread can see it before the object's constructor is finished. If this is followed, then when the object is seen by another thread, that thread will always see the correctly constructed version of that object's final fields.

允许 this 逃脱的危险在于它可能在完全构建之前就被发现了。在这种情况下,这不是问题,因为可运行对象在 start() 被调用之前不会执行,这必须在构造函数完成之后。

此外,除了 final 字段保证之外,在 mainBoard 的分配和执行之间至少有两个额外的 happens-before 障碍可运行的。一种是通过 the last thread entering the barrier, which happens-before any action in the started thread. Then there is the actual call to CylicBarrier.await(), which happen[s]-before actions that are part of the barrier action.

调用 Thread.start()

所以我会说代码非常安全。