【翻译九】新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:

以上是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".

像第二个构造器一样,通过引用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引用的对象的成员字段的写,与随后在构造函数外把这个被构造对象的引用赋值给一个引用变量,这两个操作不能重排序。

转载于:https://my.oschina.net/u/3035165/blog/867217

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值