最终关键字对并发的保证究竟是什么?

use*_*842 40 java concurrency multithreading

我想我已经读过,字段上的final关键字保证如果线程1实例化包含该字段的对象,那么如果线程2具有对该对象的引用,则线程2将始终看到该字段的初始化值(假设它是正确建造).它还在JLS中说过

[Thread 2]还将看到那些最终字段引用的任何对象或数组的版本,这些字段至少与最终字段一样是最新的.(JLS第17.5节)

这意味着如果我有A级

class A {
  private final B b = new B();
  private int aNotFinal = 2;
  ...
Run Code Online (Sandbox Code Playgroud)

和B级

class B {
  private final int bFinal = 1;
  private int bNotFinal = 2;
  ...
Run Code Online (Sandbox Code Playgroud)

然后aNotFinal不保证在线程2获得对类A的引用时被初始化,但字段bNotFinal是,因为B是由JLS中指定的最终字段引用的对象.

我有这个权利吗?

编辑:

如果我们有两个线程同时在类C的同一个实例上执行getA(),那么可能发生这种情况的情况就是这样

class C {
  private A a;

  public A getA(){
    if (a == null){
      // Thread 1 comes in here because a is null. Thread B doesn't come in 
      // here because by the time it gets here, object c 
      // has a reference to a.
      a = new A();
    }
    return a; // Thread 2 returns an instance of a that is not fully                     
              // initialized because (if I understand this right) JLS 
              // does not guarantee that non-final fields are fully 
              // initialized before references get assigned
  }
}
Run Code Online (Sandbox Code Playgroud)

jav*_*ter 18

你说的是真的.

将字段标记为final会强制编译器在构造函数完成之前完成字段的初始化.但是对于非最终字段没有这样的保证.这可能看起来很奇怪,但是编译器和JVM为了优化目的而做了许多事情,例如重新排序指令,导致这些事情发生.

final关键字还有很多好处.来自Java Concurecncy in Practice:

不能修改最终字段(尽管它们引用的对象如果可变则可以修改),但它们在Java内存模型下也有特殊的语义.使用final字段可以保证初始化安全性(参见第3.5.2节),它允许在不同步的情况下自由访问和共享不可变对象.

书中说:

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

  • 从静态初始化程序初始化对象引用;
  • 将对它的引用存储到volatile字段或AtomicReference中;
  • 将对它的引用存储到正确构造的对象的最终字段中; 要么
  • 将对它的引用存储到由锁正确保护的字段中.


aio*_*obe 12

我认为您的问题由JLS在第17.5.1节:最终字段的语义:

给定写入w,冻结f,动作a(不是最终字段的读取),f的最终字段的读取r 1和读取r 2使得hb(w,f),hb(f,a),mc(a,r 1)和解引用(r 1,r 2),然后当确定r 2可以看到哪些值时,我们考虑hb(w,r 2).

让我们根据这个问题细分:

  • w:是bNotFinal线程1 的写入
  • f:是冻结的冻结动作b
  • a:发布A对象引用
  • r 1:线程2 读取b(由f冻结)
  • r 2:b.bNotFinal线程2 的一堆

我们注意到了

  • hb(w,f):写入bNotFinal在冻结之前发生b
  • hb(f,a):A引用在构造函数完成后发布(即冻结后)
  • mc(a,r 1):线程2在读取A之前读取参考A.b
  • dereferences(r 1,r 2):线程2取消引用b访问的值b.bNotFinal

以下句子......

"那么当确定r 2可以看到哪些值时,我们考虑hb(w,r 2)"

......然后翻译成

当确定读取可以看到哪些值时,b.bNotFinal我们认为bNotFinal线程1 的写入发生在读取之前b.bNotFinal.

即线程2是保证看到价值2b.bNotFinal.


Bill Pugh的相关引言:

能够看到字段的正确构造值是很好的,但如果字段本身是引用,那么您还希望代码查看它指向的对象(或数组)的最新值.如果您的字段是最终字段,则也可以保证.因此,您可以拥有一个指向数组的最终指针,而不必担心其他线程看到数组引用的正确值,但是数组内容的值不正确.同样,在这里"正确",我们的意思是"对象的构造函数结束时的最新",而不是"最新的可用值".

特别是这是对@supercat的一个直接答案,它提出了关于错误同步的String引用共享.