Yes, it is allowed.
Mainly exposed on the already quoted sections of the JMM
:
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.
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.
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**
*
So yes, it's possible, as far as is allowed. Last paragraph is full of suggestions of how-not-to-do things; Whenever someone says avoid doing X, then is implicit that X can be done.
What if... reflection
The other answers correctly point out the requirements for the final fields to be correctly seen by other threads, such as the freeze at the end of the constructor, the chain, and so on. These answers offer a deeper understanding of the main issue and should be read first. This one focuses on a possible exception to these rules.
The most repeated rule/phrase may be this one here, copied from Eugene's answer (which shouldn't have any negative vote btw):
An object is considered to be completely initialized when its constructor finishes. A thread that can only see a reference to an
object after that object has been completely initialized is
guaranteed to see the correctly [assigned/loaded/set] values for that object's
final fields.
Note that I changed the term "initialized" with the equivalent terms assigned, loaded, or set. This is in purpose, as the terminology may mislead my point here.
Another proper statement is the one from chrylis -cautiouslyoptimistic-:
The "final freeze" happens at the end of the constructor, and from
that point on all reads are guaranteed to be accurate.
Subsequent Modification of final
Fields
These statements are not only correct, but also backed by the JLS
. I don't intend to refute them, but just add some little extra information regarding an exception to this law: reflection. That mechanism that, among other things, can change a final field's value after being initialized.
Freeze of a final
field occurs at the end of the constructor in which the final
field is set, that's completely true. But there's another trigger for the freeze operation that hasn't been taken into account: Freeze of a final
field also occurs initializing/modifying a field via reflection (JLS 17.5.3):
Freezes of a final field occur both at the end of the constructor in
which the final field is set, and immediately after each modification
of a final field via reflection.
Reflective operations on final
fields "break" the rule: after the constructor being properly finished, all reads of the final
fields are still NOT guaranteed to be accurate. I'd try to explain.
Let's imagine all the proper flow has been honored, the constructor's been initialized and all final
fields from an instance are correctly seen by a thread. Now it's time to make some changes on those fields via reflection (just imagine this is needed, even if unusual, I know..).
The previous rules are followed and all threads wait until all fields have been updated: just as with the usual constructor scenario, the fields are only accessed after being freezed and the reflective operation been correctly finished. This is where the law is broken:
If a final field is initialized to a constant expression (§15.28) in
the field declaration, changes to the final field may not be observed,
since uses of that final field are replaced at compile time with the
value of the constant expression.
This is telling: even if all rules were followed, your code won't correctly read the final
field's assigned value, if that variable is a primitive or String and you initialized it as a constant expression in the fields declaration. Why? Because that variable is just a hardcoded value for your compiler, which won't ever check again that field nor its changes, even if your code properly updated the value in runtime execution.
So, let's test it:
public class FinalGuarantee
{
private final int i = 5; //initialized as constant expression
private final long l;
public FinalGuarantee()
{
l = 1L;
}
public static void touch(FinalGuarantee f) throws Exception
{
Class<FinalGuarantee> rfkClass = FinalGuarantee.class;
Field field = rfkClass.getDeclaredField("i");
field.setAccessible(true);
field.set(f,555); //set i to 555
field = rfkClass.getDeclaredField("l");
field.setAccessible(true);
field.set(f,111L); //set l to 111
}
public static void main(String[] args) throws Exception
{
FinalGuarantee f = new FinalGuarantee();
System.out.println(f.i);
System.out.println(f.l);
touch(f);
System.out.println("-");
System.out.println(f.i);
System.out.println(f.l);
}
}
Output:
5
1
-
5
111
The final int i
was correctly updated at runtime, and to check it, you could debug and inspect the object's fields values:
Both i
and l
were correctly updated. So what's happening with i
, why is still showing 5? Because as stated on the JLS
, the field i
is replaced directly at compile time with the value of the constant expression, which in this case, is 5.
Every consequent read of the final field i
will then be INCORRECT, even if all previous rules were followed. The compiler will never check again that field: When you code f.i
, it won't access any variable of any instance. It will just return 5: the final field is just hardcoded at compile-time and if an update is made on it on runtime, it will never, ever be correctly seen again by any thread. This breaks the law.
As proof of the correct update of the fields at runtime:
Both 555
and 111L
are pushed into the stack and the fields get their newly assigned values. But what happens when manipulating them, such as printing their value?
l
was not initialized to a constant expression nor in the field declaration. As a result, isn't affected by 17.5.3 's rule. The field is correctly updated and read from outer threads.
i
, however, was initialized to a constant expression in the field declaration. After the initial freeze, there's no more f.i
for the compiler, that field will never be accessed again. Even if the variable is correctly updated to 555 in the example, every try to read from the field has been replaced by the harcoded constant 5; regardless any further change/update made on the variable, it will always return five.
16: before the update
42: after the update
No field access, but just a "yeah that's 5 for sure, return it". This implies that a final
field is not ALWAYS guaranteed to be correctly seen from outer threads, even if all protocols were followed.
This affects primitives and Strings. I know it's an unusual scenario, but it's still a possible one.
Some other problematic scenarios (some also related to the synchronize issue quoted on the comments):
1- If not correctly synchronized
with the reflective operation, a thread could fall into a race condition in the following scenario:
final boolean flag; // false in constructor
final int x; // 1 in constructor
- Let's assume the reflection operation will, in this order:
1- Set flag to true
2- Set x to 100.
Simplification of the reader thread's code:
while (!instance.flag) //flag changes to true
Thread.sleep(1);
System.out.println(instance.x); // 1 or 100 ?
As a possible scenario, the reflective operation didn't have enough time to update x
, so the final
int x
field may or not be correctly read.
2- A thread could fall into a deadlock in the following scenario:
final boolean flag; // false in constructor
- Let's assume the reflection operation will:
1- Set flag to true
Simplification of the reader thread's code:
while (!instance.flag) { /*deadlocked here*/ }
/*flag changes to true, but the thread started to check too early.
Compiler optimization could assume flag won't ever change
so this thread won't ever see the updated value. */
I know this is not a specific issue for final fields, but just added as a possible scenario of incorrect read flow of these type of variables. These last two scenarios would just be a consequence of incorrect implementations but wanted to point them out.