编程后端开发者

解释一下什么是 Java 中的安全发布,它在多线程中有什么用,以及如何保证它。

用 Hintsage AI 助手通过面试

答案

安全发布(Safe Publication)是指在多个线程之间安全传播对象的保证:如果一个线程创建并初始化了一个对象,则另一个线程总是能看到完全构造的对象,而不是其部分初始化的状态。

没有安全发布可能会导致竞争条件:一个线程看到对象未初始化的部分,即使构造函数已经完成。

保证安全发布的方法:

  • 使用 final 字段 - 它们的值在构造器之后对其他线程是可见的。
  • 通过 volatile 变量或 AtomicReference 发布对象。
  • 通过线程安全的容器(例如,来自 java.util.concurrent 的集合)发布对象。
  • 在创建和读取对象时使用 synchronized。

示例(不安全):

public class Holder { private int n; public Holder(int n) { this.n = n; } } Holder holder; void publish() { holder = new Holder(42); } // 安全性无法保证

可能会看到未完全构造的对象!

示例(安全):

volatile Holder holder; void publish() { holder = new Holder(42); } // 读取 holder 也将是安全的

陷阱问题

如果对象中的所有字段都是 final,这是否总能保证安全发布?

答案: 不,只有当对新对象的引用在构造函数结束之前被发布。如果引用在构造函数完成之前传递给其他线程 - 字段可能未完全初始化。应该避免在构造器执行期间发布 this 或对未完全构造对象的引用。

示例(危险!):

public class PublishEscape { public static PublishEscape instance; public PublishEscape() { instance = this; // 不好!this 在构造函数结束之前发布 } }

历史

开发者在构造函数完成之前将链接分配给通过 static 访问的字段。结果,其他线程获得了部分构造的对象,这导致了生产中的不可预测行为。


历史

在 Web 应用程序中,单例对象是在没有额外同步的情况下创建的。在负载下,一部分线程获得 null 或初始化不正确的字段,导致间歇性 NullPointerException。


历史

在库中使用普通 List 作为线程之间的缓存 - 缺乏安全发布,初始化未能保证对新线程的可见性。最终缓存工作混乱,破坏了数据完整性。