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

时间:2016-10-27 21:05:31

标签: java concurrency java-memory-model

Java Concurrency in Practice 有以下示例:

@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变量中存储的最新引用。

同时它说

  

任何线程都可以安全地使用不可变对象而无需额外的       同步,即使没有使用同步来发布它们。

对我而言,它说我们实际上并不需要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并且永远不会看到创建的实例吗?

有人可以解释一下吗?

1 个答案:

答案 0 :(得分:2)

  

根据我的理解,使用volatile确保所有线程都能看到存储在缓存变量中的最新引用。

这是正确的,但这个概念适用于变量而JCiP引用的语句

  

任何线程都可以安全地使用不可变对象而无需额外的   同步,即使没有使用同步来发布它们。

不适用于变量,而是适用于对象本身。这意味着一个线程总是会看到一个完全构造的对象而没有任何数据竞争,这与它的发布方式无关,因为它是JCiP状态

  

可以通过任何机制发布不可变对象。

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

  

这是不是意味着其他线程可以将f视为null并且永远不会看到创建的实例?

这是对的。如果一个帖子调用writer()其他帖子可能会将f视为nullf.y视为0,因为它不尊重安全发布:

  

3.5.3。安全出版习语

     

安全地发布对象,包括对象的引用和   必须同时使对象的状态对其他线程可见。   正确构造的对象可以通过以下方式安全地发布:

     
      
  • 从静态初始化程序初始化对象引用;
  •   
  • 将对它的引用存储到易失性字段或AtomicReference中;
  •   
  • 将对它的引用存储到正确构造的>的最终字段中。宾语;或
  •   
  • 将对它的引用存储到由锁定正确保护的字段中。
  •