Java 中的对象初始化是原子的吗?

Is object initialization atomic in Java?

我知道 myObject1 = myObject2 是原子的,但下面这行代码是原子的吗?

Object obj1 = new Object();

真的可以在初始化引用的过程中挂起吗?

它不是原子的。没有什么可以阻止您将 sleep 放入 Object 构造函数、无限循环或在构造函数中抛出异常。

注意:虽然可能,但这些都是anti-patterns,不推荐。

这一行不是原子的。发生两件事:首先,创建新的 Object(通常不是原子的),然后将对该对象的引用分配给变量 obj1。比方说,Thread1Thread2 执行的代码行观察了随机时刻变量的状态。 Thread2 可以观察到三种状态(为简单起见,假设对象创建本身是原子的),

  1. obj1 == null,未创建对象
  2. obj1 == null,对象已创建,但对它的引用尚未分配给变量
  3. obj1 != null,行完全执行

原子性表明 Thread2 可能只观察状态 1 和 3,但必须强制执行。使其成为原子的一种方法是通过同步(在此示例中 obj1 是一个 class 字段):

public synchronized Object getObj() {
    if (obj1 == null)
        obj1 = new Object();

    return obj1;
}

调用此方法的第一个线程将初始化该字段,以后的调用将简单地 return 变量。由于互斥,一个线程不可能在对象创建后在obj1中观察到null,因为不同线程的方法调用不能重叠。

引用分配是原子的。在任何情况下都不会部分分配引用本身(这显然会导致 VM 有时崩溃)。它将为空或 not-null。在分配引用之前对象的构造不是原子的,但在构造过程完成之前,您将无法访问要分配给引用的新对象。

有趣的是,引用分配可能发生在与主内存不同步的芯片缓存内存中。这意味着其他线程不会立即看到分配。有两种方法可以确保引用分配被推送到主内存,其他线程可以立即看到它。 (1) 将引用声明为 volatile 并且 (2) 将引用赋值放在同步块中(退出同步块会将缓存的引用提交到主内存)