Final update: so my first conclusion happened to be right, just my reasoning was faulty :-( I re-edited my answer to make it somewhat coherent, not to hide the traces of my earlier blunder.
Conclusion
As @Wyzard pointed out, even though there is no way to change bar
after construction, Foo
is still not thread safe. The problem is not atomicity but visibility. If thread 1 is changing the value of bar
in the constructor (from its default value of 0), there is no guarantee when other threads will get to see the new value (or whether they see it at all).
So foo
looks like an immutable object. Quoting from Java Concurrency in Practice, section 3.4:
An object is immutable if:
- Its state cannot be modified after construction;
- All its fields are final; and
- It is properly constructed (the this reference does not escape during construction).
Foo
looks OK on 1) and 3), but not 2). And that is a crucial point, due to the reasoning above. Declaring a variable final
is one way of ensuring its visibility between different threads. The other means are declaring bar
volatile
, or synchronizing its access method(s). But of course, in case of an immutable object, neither of these would make much sense.
Final Fields
So why do final
fields guarantee visibility? Answer from Java Concurrency in Practice, section 3.5.2:
Because immutable objects are so important, the JavaMemory Model offers a special guarantee of initialization safety for sharing immutable objects. As we've seen, that an object reference becomes visible to another thread does not necessarily mean that the state of that object is visible to the consuming thread. In order to guarantee a consistent view of the object's state, synchronization is needed.
Immutable objects, on the other hand, can be safely accessed even when synchronization is not used to publish the object reference. For this guarantee of initialization safety to hold, all of the requirements for immutability must be met: unmodi-fiable state, all fields are final, and proper construction. [...]
Immutable objects can be used safely by any thread without additional synchronization, even when synchronization is not used to publish them.
This guarantee extends to the values of all final fields of properly constructed objects; final fields can be safely accessed without additional synchronization. However, if final fields refer to mutable objects, synchronization is still required to access the state of the objects they refer to.
And what happens if the field is not final? Other threads may silently see a stale value of the field. There is no exception or any kind of warning - that is one reason why these kinds of bugs are so difficult to trace.