Reputation: 1975
I think I've read that the final keyword on a field guarantees that if thread 1 instantiates the object containing the field, then thread 2 will always see the initialized value of that field if thread 2 has a reference to the object (provided it was properly constructed). It also says in the JLS that
[Thread 2] will also see versions of any object or array referenced by those final fields that are at least as up-to-date as the final fields are. (section 17.5 of JLS)
That implies that if I have class A
class A {
private final B b = new B();
private int aNotFinal = 2;
...
and class B
class B {
private final int bFinal = 1;
private int bNotFinal = 2;
...
then aNotFinal is not guaranteed to be initialized by the time thread 2 gets a reference to class A, but field bNotFinal is, because B is an object referenced by a final field, as specified in the JLS.
Do I have this right?
Edit:
A scenario where this could happen would be if we have two threads concurrently executing getA() on the same instance of a class C
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
}
}
Upvotes: 45
Views: 9258
Reputation: 2824
It's worth mentioning that final
here serves the same purpose as volatile
in terms of visibility of values to threads. That said, you cannot use both final
and volatile
on a field as they are redundant to each other. Back to your question. As others have pointed out your assumption is wrong as JLS only guarantees the visibility of the reference to B, not the non-final fields defined in B. However, you can make B behaves in the way you want it to behave. One solution is to declare bNotFinal
as volatile
if it can't be final
.
Upvotes: 3
Reputation: 421160
I think your question is answered by JLS right below the part you quote, in Section 17.5.1: Semantics of final Fields:
Given a write w, a freeze f, an action a (that is not a read of a final field), a read r1 of the final field frozen by f, and a read r2 such that hb(w, f), hb(f, a), mc(a, r1), and dereferences(r1, r2), then when determining which values can be seen by r2, we consider hb(w, r2).
Let's break it down in terms of the question:
bNotFinal
by Thread 1b
A
object referenceb
(frozen by f) by Thread 2b.bNotFinal
by Thread 2We note that
bNotFinal
happens before the freeze of b
A
reference is published after the constructor is finished (i.e. after the freeze)A
reference before reading A.b
b
by accessing b.bNotFinal
The following sentence...
"then when determining which values can be seen by r2, we consider hb(w, r2)"
...then translates to
when determining which values can be seen by the read of
b.bNotFinal
we consider that the write tobNotFinal
by Thread 1 happens before the read ofb.bNotFinal
.
I.e. Thread 2 is guaranteed to see the value 2
for b.bNotFinal
.
A relevant quote by Bill Pugh:
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".
In particular this is a direct answer to the example @supercat brought up regarding illsynchronized sharing of String
references.
Upvotes: 13
Reputation: 120988
There is a memory barrier inserted after a final assignment (it's a store fence), this is how you guarantee that the other threads will see the value that you assign. I like the JLS and how it says that things are done with happens-before/after and guarantees of the final, but to me, the memory barrier and it's effects are much simpler to grasp.
You should really read this : Memory barriers
Upvotes: 2
Reputation: 3135
"JSR 133 (Java Memory Model) FAQ, Jeremy Manson and Brian Goetz, February 2004" describes how does final
field work.
QUOTE:
The goals of JSR 133 include:
- A new guarantee of initialization safety should be provided. If an object is properly constructed (which means that references to it do not escape during construction), then all threads which see a reference to that object will also see the values for its final fields that were set in the constructor, without the need for synchronization.
Upvotes: 1
Reputation: 1097
What you are saying is true.
Marking a field as final forces the compiler to complete initialization of the field before the constructor completes. There is no such guarantee however for non-final fields. This might seem weird, however there are many things done by the compiler and JVM for optimization purposes such as reordering instructions, that cause such stuff to occur.
The final keyword has many more benefits. From the Java Concurecncy in Practice:
Final fields can't be modified (although the objects they refer to can be modified if they are mutable), but they also have special semantics under the Java Memory Model. It is the use of final fields that makes possible the guarantee of initialization safety (see Section 3.5.2) that lets immutable objects be freely accessed and shared without synchronization.
The Books says then:
To publish an object safely, both the reference to the object and the object's state must be made visible to other threads at the same time. A properly constructed object can be safely published by:
- Initializing an object reference from a static initializer;
- Storing a reference to it into a volatile field or AtomicReference;
- Storing a reference to it into a final field of a properly constructed object; or
- Storing a reference to it into a field that is properly guarded by a lock.
Upvotes: 22
Reputation: 2143
class A {
private final B b = new B();
}
The above line only guarantees that b will be initialized when you access b from an instance of A. Now the detail of b's initialization or any instantiation of B is completely dependent on how B is defined and in this case it may get initialized as per JLS or may not.
So, if you do A a = new A(); from one thread and somehow manage to read a.b from another thread, it is guaranteed you will not see null if a is not null but b.bNotFinal may be still zero.
Upvotes: 2