了解 JVM 对不可变对象的保证

Understanding JVM guarantees for immutable objects

Java 并发实践 有以下例子:

@ThreadSafe
public class VolatileCachedFactorizer implements Servlet {
    private volatile OneValueCache cache = new OneValueCache(null, null);

    public void service(ServletRequest req, ServletResponse resp) {
        BigInteger i = extractFromRequest(req);
        BigInteger[] factors = cache.getFactors(i);
        if (factors == null) {
            factors = factor(i);
            cache = new OneValueCache(i, factors);
        }
            encodeIntoResponse(resp, factors);
        }
    }
}

OneValueCache 是不可变的。

据我所知,使用 volatile 可确保所有线程都能看到存储在 cache 变量中的最新引用。

同时表示

Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.

对我来说,它说我们实际上不需要上面用于同步的 volatile

JLS还有下面的例子:

class FinalFieldExample {
    final int x;
    int y; 

    static FinalFieldExample f;

    public FinalFieldExample() {
        x = 3;
        y = 4;
    }

    static void writer() {
        f = new FinalFieldExample();
    }

    static void reader() {
        if (f != null) {
            int i = f.x; // guaranteed to see 3
            int j = f.y; // could see 0
        }
    }
}

他们不使用 volatile 字段 f。这不是意味着其他线程可以将 f 视为 null 而永远看不到创建的实例吗?

有人可以解释一下吗?

From what I understand using volatile ensures that all threads see up-to-date reference stored in the cache variable.

没错,但是这个概念适用于变量 while the statement quoted from JCiP

Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.

不适用于变量,但适用于对象本身。这意味着一个线程将始终看到一个完全构造的对象,而不会发生任何数据竞争,这与它的发布方式无关,因为正如 JCiP 所述

Immutable objects can be published through any mechanism.

现在关于你的第二个例子:

Doesn't it mean that other threads can see f as null and never see the created instance?

没错。如果一个线程调用 writer(),其他线程可能会将 f 视为 null 或将 f.y 视为 0,因为它不遵守安全发布:

3.5.3. Safe Publication Idioms

To publish an object safely, both the reference to the object and the object's state must be made visible to other threads at the same time. A properly constructed object can be safely published by:

  • Initializing an object reference from a static initializer;
  • Storing a reference to it into a volatile field or AtomicReference;
  • Storing a reference to it into a final field of a properly constructed > object; or
  • Storing a reference to it into a field that is properly guarded by a lock.