最后的领域,参考和安全出版物

时间:2014-07-04 07:49:05

标签: java multithreading final java-memory-model

考虑下面不使用volatile的双重检查锁定的非传统实现:

public class ValueProvider {
  private static State state = new Initial();

  public static Value getValue() {
      return state.getValue();
  }

  private static class Initial implements State {
      @Override
      public synchronized Value getValue() {
          if (state instanceof Initial) {
              Value value = new Value();
              value.x = 1;
              value.y = 2; 
              state = new Initialized(value);
              return value;
          } else {
              return state.getValue();
          }
      }
  }

  private static class Initialized implements State {
      private final Value value;

      private Initialized(Value value) {
          this.value = value;
      }

      @Override
      public Value getValue() {
          return value;
      }
  }

  private interface State {
      Value getValue();
  }

  public static final class Value {
      private int x;
      private int y;

      public int getX() {
          return x;
      }

      public int getY() {
          return y;
      }
  }

}

这段代码是否是线程安全的?

具体来说,我问的是最终字段及其给出的保证,所以问题可能会被重新制定,因为某些线程可能获得一个非初始化的Value实例?

更新:删除了有关setter的提及,以便在发布后只能读取

2 个答案:

答案 0 :(得分:0)

不,这不是线程安全的。在读取ValueProvider.state时没有内存障碍,在Value上根本没有。

Java兼容性的经验法则是,在写入时,读取需要存储器障碍。

在Java中添加内存屏障的唯一方法是:

  • 同步
  • 易失性
  • 类初始化(由jvm隐含)
  • 原子
  • 不安全

对于大多数事情,Hotspot会忽略final关键字,并且更喜欢自己推断它。然而,最终影响JMM的地方与类构造和内联有关。最后字段的重新排序规则包含在您已经提到的食谱中。它没有提到最后的课程。食谱说:

Loads and Stores of final fields act as "normal" accesses 
with respect to locks and volatiles, but impose two additional reordering
     

1)最终字段的存储(在构造函数内部,如果字段是引用,则此最终可以引用的任何商店,不能与后续存储重新排序

     

2)对于包含最终字段的对象的初始加载,无法对最终字段的初始加载(即线程的第一次遇到)进行重新排序。

答案 1 :(得分:0)

嗯,除了你的方法过于复杂Bohemian ♦ has pointed out之外,它还可以用于发布。如果两个线程同时访问getValue(),则只有一个线程可以进入synchronized块。由于synchronized字段初始化保证,另一个将在Initialized数据块上被阻止,或者看到value的实例具有正确初始化的final字段。

但是,它仍然无效,因为类Value的实例是 mutable 而您的注释// getters and setters表示实例将在构造后发生变异。在这种情况下,整个final字段初始化保证是没有意义的,因为Value类不是线程安全的。您可能会看到xy的默认值,但您永远不会知道您将看到的有关后续修改的值以及(x,{{1 }})不一定一致。