// Not really how java.util.concurrent.Semaphore is implemented
@ThreadSafe
public class SemaphoreOnLock {
private final Lock lock = new ReentrantLock();
//
I honestly don't see any valid use for the lock here, other than the fact that it introduces a memory fence. int
assignments are atomic on 32/64 bit anyway.
the half-constructed-object is not visible to other threads
It is not true. The object is visible to other threads at the time of construction if it has any non final/volatile fields. Therefore, other threads might see a default value for permits
i.e 0
which might not be consistent with the current thread.
The Java memory model offers a special guarantee of initialization safety for immutable objects (object with only final fields). An object reference visible to another thread does not necessarily mean that the state of that object is visible to the consuming thread - JCP $3.5.2
From Listing 3.15 of Java Concurrency in Practice:
While it may seem that field values set in a constructor are the first values written to those fields and therefore that there are no "older" values to see as stale values, the Object constructor first writes the default values to all fields before subclass constructors run. It is therefore possible to see the default value for a field as a stale value.
(Just clarifying it for my own poor head - the other answers are correct).
Instances of this hypothetical SemaphoreOnLock
class are intended to be shared. So thread T1
fully constructs an instance, and puts it somewhere where thread T2
can see it and invoke some method which requires reading the permits
field. Some important things to note about the permits
field:
0
0
), by thread T1
volatile
final
(which makes it kind of like a 'one shot volatile')Therefore, if we want T2
to read the value that T1
last wrote, we need to synchronize. We have to do this in the constructor, just as we must in every other case. (That fact of it being an atomic assignment or not doesn't affect this visibility issue). The strategy of confining the constructed SemaphoreOnLock
to a single thread doesn't work for us, because the whole idea of making it @Threadsafe
is so that we can safely share it.
What this example illustrates is that "being threadsafe" applies to the construction of an object as well, when setting any non-static, non-final, non-volatile field to a value other than its default value.
Of course, we're not obliged to even think about this when we've got a @NotThreadsafe
class. If the caller constructs us and decides to share us between two threads, then the caller must arrange for appropriate synchronization. In that scenario, we can do whatever we like in the constructor without worrying about visibility concerns - that's somebody else's problem.