安全发布(Safe Publication)是指在多个线程之间安全传播对象的保证:如果一个线程创建并初始化了一个对象,则另一个线程总是能看到完全构造的对象,而不是其部分初始化的状态。
没有安全发布可能会导致竞争条件:一个线程看到对象未初始化的部分,即使构造函数已经完成。
保证安全发布的方法:
示例(不安全):
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 作为线程之间的缓存 - 缺乏安全发布,初始化未能保证对新线程的可见性。最终缓存工作混乱,破坏了数据完整性。