Reputation: 10308
I recently started learning C# and went straight for the memory model. C# and Java have similar (though perhaps not identical) thread safety guarantees regarding reads and writes of volatile
fields. But unlike writes to final
fields in Java, writes to readonly
fields in C# do not provide any specific thread safety guarantee. Thinking about how thread safety works in C# led me to doubt whether there is any real advantage to final
fields behaving the way they do in Java.
I learned about the supposed importance of final
three years ago. I asked this question and got a detailed answer that I accepted. But now I think it's wrong, or at least irrelevant. I still think fields should be final
whenever possible, just not for the reasons commonly believed.
The value of a final
field is guaranteed to be visible to any other thread after the constructor returns. But the reference to the object itself must be published in a thread safe manner. If the reference is published safely, then the visibility guarantee of final
becomes redundant.
I considered the possibility that it has something to do with public static
fields. But logically, a class loader must synchronize the initialization of a class. Synchronization makes the thread safety of final
redundant.
So I'm putting forth the heretical idea that the only real value of final
is to make immutability self-documenting and self-enforcing. In practice, private non-final fields (and in particular, array elements) are perfectly thread safe as long as they are not modified after the constructor returns.
Am I wrong?
Edit: Paraphrasing section 3.5 of Java Concurrency in Practice,
Two things can go wrong with improperly published objects. Other threads could see a stale value for the reference, and thus see a null reference or other older value even though a value has been set. But far worse, other threads could see an up-to-date value for the reference, but stale values for the state of the object.
I understand how final
fields solve the second problem, but not the first problem. The highest voted answer so far argues that the first problem is not a problem.
Edit 2: This question arises from a confusion of terminology.
Like the asker of a similar question, I've always understood the term "safe publication" to mean that both an object's internal state and the reference to the object itself are guaranteed visible to other threads. In favor of this definition, Effective Java cites Goetz06, 3.5.3 in defining "safe publication" as (emphasis added)
Transferring such an object reference from one thread to others
Also in favor of this definition, note that the section of Java Concurrency in Practice paraphrased above refers to potentially stale references as being "improperly published."
Whatever you call it, I didn't think that unsafely publishing a reference to an immutable object could ever be useful. But according to this answer, it can. (The example given there is a primitive value, but the same principle could apply to reference values.)
Upvotes: 0
Views: 166
Reputation: 120988
I've read your question a couple of times and still have some issues understating it. I will just try to augment the other answer - which is in fact correct. final
in Java is all about re-orderings (or happens-before as you call it).
First this is guaranteed by the JLS
, see Final Field Semantics. In the example you can see that a single final field is guaranteed to be correctly seen by other threads, while the other is not. The JLS is correct, but under the current implementation a single field to be final
would be enough - read further.
Every single write to a final field inside a constructor is followed by two memory barriers
- StoreStore
and LoadStore
(thus the name of happens-before); because the store to a final field will happen before read to the same field - guaranteed via memory barriers.
But the current implementation does not do that - meaning that the memory barriers do not happen after every single write to a final - they happen at the end of the constructor, see this. You will see this line that is important:
_exits.insert_mem_bar(Op_MemBarRelease, alloc_with_final());
Op_MemBarRelease
being the actual LoadStore|StoreStore
barrier, thus under the current implementation it is enough to have a single field to be final for all others to be safely published as well. But, of course, do that at your own risk.
volatile
in this context would not make publication enough - since it would simply not introduce the necessary barriers, you can read a bit more here.
Note that the reference publishing is not a concern, simply because the JLS says: Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
.
Upvotes: 0
Reputation: 50746
But the reference to the object itself must be published in a thread safe manner. If the reference is published safely, then the visibility guarantee of
final
becomes redundant.
The first sentence is wrong; the second is therefore irrelevant. final
may be redundant in the presence of other safe publication techniques, like synchronization or volatile
. But the point of immutable objects is that they're inherently thread-safe, meaning they'll be seen in a consistent state regardless of how the reference is published. So you don't need those other techniques in the first place, at least as far as safe publication is concerned.
EDIT: OP correctly points out that there's some ambiguity around the term "safe publication". In this context I'm referring to consistency of the object's internal state. The issue of visibility as it affects the reference is, in my opinion, a valid but separate concern.
Upvotes: 4