【翻译九】新JMM下的final

How do final fields work under the new JMM?

The values for an object's final fields are set in its constructor. Assuming the object is constructed "correctly", once an object is constructed, the values assigned to the final fields in the constructor will be visible to all other threads without synchronization. In addition, the visible values for any other object or array referenced by those final fields will be at least as up-to-date as the final fields.数组

一个对象的final字段是在它的构造器中赋值的。假如这个对象被正确的构造了,那么即便不用同步,其余线程也能看到这个这个final字段的值。另外,引用这个final字段的对象或者数组都会即便更新。安全

What does it mean for an object to be properly constructed? It simply means that no reference to the object being constructed is allowed to "escape" during construction. (See Safe Construction Techniques for examples.) In other words, do not place a reference to the object being constructed anywhere where another thread might be able to see it; do not assign it to a static field, do not register it as a listener with any other object, and so on. These tasks should be done after the constructor completes, not in the constructor.并发

但什么是正确的构造呢?简而言之,就是在构造过程当中,不能有任何引用指向这个对象(参考安全构造技术示例)。换句话说,不要在其它线程能看到的地方引用一个正在构建的对象,不要将引用设成静态字段,不要将引用与其它对象注册成监听器,等等。这些任务应该在构建完成以后,而不是在构造过程当中。函数

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;
          int j = f.y;
        }
      }
    }

The class above is an example of how final fields should be used. A thread executing reader is guaranteed to see the value 3 for f.x, because it is final. It is not guaranteed to see the value 4 for y, because it is not final. If FinalFieldExample's constructor looked like this:this

以上是final字段的正确用法。任何线程调用reader()获得的x必定是3,由于x是final的,但y就不必定是4。若是上栗的构造器是下面这样的:线程

public FinalFieldExample() { // bad!
      x = 3;
      y = 4;
      // bad construction - allowing this to escape
      global.obj = this;
    }

then threads that read the reference to this from global.obj are not guaranteed to see 3 for x.翻译

那么,因为构造过程当中,表明当前对象的this被global.obj引用,x的值就不能保证必定是3了。(x=3, y=4 和 global.obj=this 可能会发生重排序)指针

The ability to see the correctly constructed value for the field is nice, but if the field itself is a reference, then you also want your code to see the up to date values for the object (or array) to which it points. If your field is a final field, this is also guaranteed. So, you can have a final pointer to an array and not have to worry about other threads seeing the correct values for the array reference, but incorrect values for the contents of the array. Again, by "correct" here, we mean "up to date as of the end of the object's constructor", not "the latest value available".code

像第二个构造器同样,经过引用this,能在构造过程当中看到x和y是否被赋予正确的值是很棒的,可是若是x(或y)自己就是一个引用,并且你想要看到x所指向的最新值,那么x必须是final的。也就是说,你能够用一个final指针指向一个数组,其余线程看到的引用必定正确,但数组的值可能不正确。正确的意思,是指对象构造结束后就是最新值,而不是最近的可用值。对象

Now, having said all of this, if, after a thread constructs an immutable object (that is, an object that only contains final fields), you want to ensure that it is seen correctly by all of the other thread, you still typically need to use synchronization. There is no other way to ensure, for example, that the reference to the immutable object will be seen by the second thread. The guarantees the program gets from final fields should be carefully tempered with a deep and careful understanding of how concurrency is managed in your code.

说了这么多,若是你用一条线程构造了一个不可变对象(全部字段都为final),想要让它对其余线程正确地可见,仍是应该用同步的,由于没有其余方法能够保证,该对象的引用会被另外一条线程看到。 并发中要想看到final的正确值,应该深思熟虑。

There is no defined behavior if you want to use JNI to change final fields.

JMM对使用JNI修改final字段没有规定。

参考资料

翻译完发现仍是不懂,又找到一篇文章《深刻理解 Java final 变量的内存模型》,final的介绍很详细。

这里总结一下从中抽出的关键点:

写final字段的重排序规则

  • JMM禁止编译器把final字段的写,重排序到构造器外面

  • 编译器会在final字段的写以后,构造函数的return以前,插入一个StoreStore屏障,这个屏障禁止处理器把final的写重排序到构造函数以外

读final字段的重排序规则

  • 编译器会在读final字段操做的前面插入一个LoadLoad屏障

  • 在一个线程中,初次读对象引用与初次读该对象包含的final字段,JMM禁止处理器重排序这两个操做

引用类型的final字段重排序规则

  • 编译器和处理器不容许,在构造函数内对final引用的对象的成员字段的写,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操做不能重排序。
相关文章
相关标签/搜索