Java 关于并发性,final关键字究竟保证了什么?
我想我已经读到,字段上的final关键字保证,如果线程1实例化包含该字段的对象,那么如果线程2引用了该对象(前提是该对象构造正确),那么线程2将始终看到该字段的初始化值。JLS中还指出 [Thread 2]还将看到所引用的任何对象或数组的版本 这些最终字段至少与最终字段一样是最新的 是的 这意味着如果我有A级Java 关于并发性,final关键字究竟保证了什么?,java,multithreading,concurrency,Java,Multithreading,Concurrency,我想我已经读到,字段上的final关键字保证,如果线程1实例化包含该字段的对象,那么如果线程2引用了该对象(前提是该对象构造正确),那么线程2将始终看到该字段的初始化值。JLS中还指出 [Thread 2]还将看到所引用的任何对象或数组的版本 这些最终字段至少与最终字段一样是最新的 是的 这意味着如果我有A级 class A { private final B b = new B(); private int aNotFinal = 2; ... B类 class B { pr
class A {
private final B b = new B();
private int aNotFinal = 2;
...
B类
class B {
private final int bFinal = 1;
private int bNotFinal = 2;
...
然后,不能保证在线程2获取对类a的引用时初始化aNotFinal,但字段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
}
}
你说的是真的 将字段标记为final将强制编译器在构造函数完成之前完成字段的初始化。但是,对于非最终字段,没有此类保证。这可能看起来很奇怪,但是编译器和JVM为了优化的目的做了很多事情,比如重新排序指令,导致了这些事情的发生 最后一个关键字还有很多好处。从Java Concurrency的实践来看: 不能修改最终字段(尽管它们引用的对象可以修改,如果它们是可变的),但是 在Java内存模型下也有特殊的语义。使用final字段使保证成为可能 初始化安全性(见第3.5.2节),允许自由访问和共享不可变对象,而无需 同步 书上说: 要安全地发布对象,对对象的引用和对象的状态都必须对其他用户可见 线程同时运行。正确构造的对象可以通过以下方式安全发布:
- 从静态初始值设定项初始化对象引用李>
- 将对它的引用存储到易失性字段或原子引用中李>
- 将对它的引用存储到正确构造的对象的最终字段中或
- 将对它的引用存储到由锁正确保护的字段中
所以,如果你做A=newa();从一个线程读取a.b,并以某种方式从另一个线程读取a.b,可以保证,如果a不为null,您不会看到null,但b.bNotFinal可能仍然为零。值得一提的是,
final
在线程的值可见性方面与volatile
具有相同的用途。也就是说,不能在字段上同时使用final
和volatile
,因为它们彼此冗余。回到你的问题上来。正如其他人指出的那样,您的假设是错误的,因为JLS只保证对B的引用的可见性,而不是B中定义的非最终字段。但是,您可以使B按照您希望的方式进行行为。一种解决方案是将bNotFinal
声明为volatile
,如果它不能是final
,我想JLS会在您引用的部分下方回答您的问题,在:
给定一个写W,一个冻结F,一个动作A(这不是一个最终字段的读取),一个由F冻结的最终字段的读R1,和一个读R2,例如Hb(w,f),Hb(f,a),MC(a,r1),和去数(R1,R2),然后当确定哪些值可以被R2看到时,我们考虑Hb(w,r2)。 让我们从问题的角度来细分:
- w:是线程1对
的写入吗bNotFinal
- f:是冻结
b
- a:发布
对象引用a
- r1:线程2读取
(被f冻结)b
- r2:线程2产生的
区域b.bNotFinal
- hb(w,f):写入
发生在bNotFinal
b
- hb(f,a)
参考在构造函数完成后(即冻结后)发布a
- mc(a,r1):线程2在读取
a.b
- 解引用(r1,r2):线程2通过访问
b.bNotFinal
当确定<代码> B.BNOTATECTON/COD>的读数可以看到哪些值时,我们认为线程1中的写到<代码> BNoTrime<代码>发生在代码< B.BNEXTATECTON/CODE> >
之前。 也就是说,保证螺纹2可以看到b.bNotFinal
的值2
比尔·普格: 查看字段正确构造的值的能力很好,但是如果字段本身是引用,那么您还希望代码能够查看它所指向的对象(或数组)的最新值。如果您的字段是最终字段,这也是有保证的。因此,您可以拥有指向数组的最终指针,而不必担心其他线程看到数组引用的正确值,但数组内容的值不正确。同样,这里的“正确”是指“截至对象构造函数结束时的最新值”,而不是“可用的最新值” 特别是,这是对@supercat示例的直接回答,该示例涉及
String
引用的不同步共享。
class A {
private final B b = new B();
}