If I decide to use a non-thread-safe collection and synchronize its access, do I need to synchronize any mutation in the constructor? For example in the following code, I under
Ok so this is what JLS §17.5.1 has to say on the topic.
First of all:
Let o be an object, and c be a constructor for o in which a final field f is written. A freeze action on final field f of o takes place when c exits, either normally or abruptly.
So we know that in our code:
public ListInConstructor() {
list = new ArrayList<>();
list.add(new Object());
} // the freeze action happens here!
So now the interesting part:
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).
So let's do this one piece at a time:
We have hb(w,f), which means we write to the final field before leaving the constructor.
r1 is the read of the final field and dereferences(r1, r2). This means that r1 reads the final field and r2 then reads some value of this final field.
We further have an action (a read or write, but not a read of the final field) which has hb(f,a) and mc(a, r1). This means that the action happens after the constructor but can be seen by the read r1 afterwards.
And consequently it states that "we consider hb(w, r2)", which means that the write has to happen before the read to a value of the final field that was read with r1.
So the way I see it, it is clear that the object added to the list will have to be visible by any thread that can read list
.
On a sidenote: HotSpot implements final field semantics by putting a memory barrier at the end of any constructor that contains a final field thereby guaranteeing this property in any case. Whether that's just an optimisation (Better to do to only a single barrier and that as far away from the write as possible) is another question.