19
// Not really how java.util.concurrent.Semaphore is implemented
@ThreadSafe
public class SemaphoreOnLock {
    private final Lock lock = new ReentrantLock();
    // CONDITION PREDICATE: permitsAvailable (permits > 0)
    private final Condition permitsAvailable = lock.newCondition();
    @GuardedBy("lock") private int permits;

    SemaphoreOnLock(int initialPermits) {
        lock.lock();
        try {
            permits = initialPermits;
        } finally {
            lock.unlock();
        }
    }

/* other code omitted.... */

I have a question about the sample above which is extracted from Java Concurrency in Practice Listing 14.12 Counting Semaphore Implemented Using Lock.

I am wondering why we need to acquire the lock in the constructor (as shown lock.lock() is invoked). As far as i know, constructor is atomic (except the reference escaped) as no other thread can get the reference, hence, the half-constructed-object is not visible to other threads. Therefore, we do not need the synchronized modifier for constructors. Besides, we do not need to worry about the memory visibility as well, as long as the object is safely published.

So, why do we need to get the ReentrantLock object inside the constructor?

Prince John Wesley
  • 62,492
  • 12
  • 87
  • 94
ben ben
  • 259
  • 3
  • 9

3 Answers3

16

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.

Matthias Braun
  • 32,039
  • 22
  • 142
  • 171
Prince John Wesley
  • 62,492
  • 12
  • 87
  • 94
  • thank you very much for answering and i highly appreciate that your answer is very detailed. however, during the construction, threads other than the constructing thread can not get the reference of the object. As other thread cannot get the object reference, they cannot read the `int` value of the object. so, other threads cannot read the stale value since they cannot even read the value. besides, if the object is safely published(i.e. static initializer/final field/volatile field/field guarded by a lock). The "stale value problem" will not occur. – ben ben May 11 '12 at 01:52
  • @benben: object reference visibility and object's state visibility are different things. both the reference of the object and the object's state must be made visible to other threads at the same time. Read Listing JCP $3.14. In your case ,`field guarded by a lock` is used. – Prince John Wesley May 11 '12 at 02:21
  • `field guarded by a lock` would means `@GuardedBy("lock") SemaphoreOnLock sol = ...;`. The lock isn't inside the constructor of the SemaphoreOnLock class. It means lock of some other classes using SemaphoreOnLock object. – ben ben May 11 '12 at 06:47
  • @benben: value of `permits` should be consistent across thread. To make it consistent, either you need to mark it as volatile(weak semantics)(since it is of type `int`) or guard it by a lock(strong semantics). the `@GuardedBy` annotation tells that the particular field is protected by lock. It is a cleaner way to convey the programmer that he/she should always use the given lock to access the field.Here, the requirement for visibility is `permit` should equal to itself. I mean, it should be consistent. You need a memory fence so that the value of permit is consistent across threads. – Prince John Wesley May 11 '12 at 08:50
  • 2
    yes, `permit` should be guarded by the lock for getter/setter methods to ensure the memory visibility and mutual exclusion among threads AFTER construction, but not DURING construction. During object construction, only one thread (the constructing thread) can reach the half-constructed-object(assume no reference escape), so mutual exclusion is not required. More, safe-publication would ensure other threads could get the latest value of the object reference (and other relative fields), so memory visibility is guaranteed. Thread-safety is ensured without the lock. – ben ben May 11 '12 at 09:10
  • @benben: if you don't have lock in this example then the object is not published safely. I didn't mean about reference escape. if you assign `SemaphoreOnLock` instance to some variable, JVM assigns the object reference to that variable and will start initializing it. so other threads can see the instance through that variable. but it is not a case for final fields that has immutable object. Did you read the `Listing $3.14 Java concurrency in practice` that explains that behavior ? – Prince John Wesley May 11 '12 at 09:22
  • 1
    Actually, you may see `Listing 3.15 Class at Risk of Failure if Not Properly Published` of Java Concurrency in Practice. **The problem here is not the Holder class itself, but that the Holder is not properly published..."** said by the author. The `SemaphoreOnLock` class is similar to the `Holder` class in `List3.15`. – ben ben May 11 '12 at 09:44
  • @benben: Yes. But my point is about improper publication. In that case, lock ensures consistency no matter whether it is actual data or the stale one. – Prince John Wesley May 11 '12 at 10:17
  • acquiring a lock inside constructor cannot help in the case of improper publication. for instance, declare `static SemaphoreOnLock sol = null;` and declare a method `public SemaphoreOnLock init(){ sol = new SemaphoreOnLock(1)}`. Even a thread invokes `init()`, other threads are not guaranteed to get the SemaphoreOnLock object. Other threads may see `null` (stale value) of the `sol` reference. In this case, the SemaphoreOnLock is not safely published and adding a lock inside constructor cannot help. – ben ben May 11 '12 at 10:34
  • @benben:Yes.I strongly agree.I'm just emphasizing on consistency, in the case of stale data, all the threads would see the stale data itself including the one which has been calling the constructor at that point in time because it is yet to update it.Memory fence protects the field from multiple access. Happy to chat with you. – Prince John Wesley May 11 '12 at 10:56
  • This class looks same but constructor is not guarded http://jcip.net/listings/SafePoint.java – gstackoverflow Mar 13 '17 at 21:33
  • You say *Object is visible to other threads at the time of construction if it has any non final/volatile fields.*, what do you mean? – Jason Law Jan 20 '20 at 09:51
0

(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:

  1. it gets initialized, in the first case, to a default value of 0
  2. it is then assigned a value (which may be other than the default of 0), by thread T1
  3. it's not volatile
  4. it's not 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.

David Bullock
  • 6,112
  • 3
  • 33
  • 43
-1

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.

Tudor
  • 61,523
  • 12
  • 102
  • 142
  • Not really. two assignments are not atomic. Non final fields are first initialized to default value and then the actual assignment will happen – Prince John Wesley May 10 '12 at 09:06
  • In that case they could have just set the field to volatile. The lock itself protects a single assignment which is atomic, so the only useful effect is the memory fence. – Tudor May 10 '12 at 09:41
  • yes volatile can be used since int is atomic. but this example has `field guarded by a lock`(memory fence) mechanism to make sure that the object's state visibility is consistent across threads. – Prince John Wesley May 11 '12 at 02:26
  • @Prince John Wesley Do you really think that volatile is not enough for across thread visibility? – gstackoverflow Mar 13 '17 at 21:10