2

I am trying to understand a particular detail in ReentrantLock::lock method. I am looking at it and seeing it as:

final void lock() {
   if (!initialTryLock()) {
       acquire(1);
   }
}

So first it tries this method : initialTryLock (I will look in NonfairSync), which does this:

  • it does a compareAndSwap(0, 1), meaning if no one holds the lock (0) and I can grab it (1), I hold the lock now.
  • if the above fails, it checks if the thread requesting the lock is the owner already.
  • if that fails it returns false, meaning I could not acquire the lock.

Let's assume the above failed. It then goes on and calls acquire in AbstractQueuedSynchronizer:

public final void acquire(int arg) {
    if (!tryAcquire(arg))
        acquire(null, arg, false, false, false, 0L);
}

It calls tryAcquire first in NonfairSync:

protected final boolean tryAcquire(int acquires) {
    if (getState() == 0 && compareAndSetState(0, acquires)) {
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

You can see that it tries to acquire the lock again, though the initialTryLock already failed. In theory, this tryAcquire could have simply returned false, right?

I see this as a potential retry, because between the calls of initialTryLock and tryAcquire, the lock might have been released. The benefit of this might be that because the next operation (after tryAcquire) fails, is the expensive enqueue of this thread. So I guess this makes sense (to retry) because of that?

Eugene
  • 117,005
  • 15
  • 201
  • 306
  • which version of java src are you mentioning? – Andrea Jan 29 '21 at 12:37
  • @Andrea my bad, I was looking at jdk-15; if that matters. – Eugene Jan 29 '21 at 14:04
  • In java 12 ReentrantLock::lock calls directly sync.acquire(1) and the function initialTryLock does not exist. So the code you mention was introduced recently. If someone would investigate, he should refer to the correct java version – Andrea Jan 29 '21 at 15:46
  • @Andrea good point. I added the proper tag. thank you – Eugene Jan 29 '21 at 15:47

2 Answers2

2

Just to add to the answer above.

tryAcquire could have simply returned false, right?

No.

This implementation:

boolean tryAcquire(int acquires) {
  return false;
}

would break the work of AbstractQueuedSynchronizer.

The reason is that tryAcquire() is the only way to take the lock in AbstractQueuedSynchronizer.

Even acquire() in the end uses tryAcquire().

So if tryAcquire() always returned false then acquire() would never acquire the lock.

And acquire() is used when several threads contend for the lock.

  • I saw the usage of `tryAcquire` in `acquire`, but as far as I can see [here](https://hg.openjdk.java.net/jdk/jdk15/file/0dabbdfd97e6/src/java.base/share/classes/java/util/concurrent/locks/AbstractQueuedSynchronizer.java#l676) it's perfectly OK for it to fail, i.e.: `acquired` to be `false`. that threw me off a little bit, initially. I looked at the code now that you mentioned a bit closer, and if `tryAcquire` would return `false`, any extending class of `AbstractQueueSynchronizer` could not acquire the lock for an `ExclusiveNode`, indeed. thank you, it all makes sense now. – Eugene Jan 31 '21 at 02:06
1
  • initialTryLock() contains reentrancy functionality:

    • javadoc:
      /**
      * Checks for reentrancy and acquires if lock immediately
      * available under fair vs nonfair rules. Locking methods
      * perform initialTryLock check before relaying to
      * corresponding AQS acquire methods.
      */
      abstract boolean initialTryLock();
      
    • source code in NonfairSync:
      final boolean initialTryLock() {
          Thread current = Thread.currentThread();
          if (compareAndSetState(0, 1)) { // first attempt is unguarded
              setExclusiveOwnerThread(current);
              return true;
          } else if (getExclusiveOwnerThread() == current) {
              int c = getState() + 1;
              if (c < 0) // overflow
                  throw new Error("Maximum lock count exceeded");
              setState(c);
              return true;
          } else
              return false;
      }
      
      Here:
      • the 1st if checks if the lock is taken (and takes the lock if it is free)
      • the 2nd if checks if the taken lock belongs to the current thread — this is the reentrancy logic.
  • tryAcquire() must be implemented by any class extending AbstractQueuedSynchronizer

    • what the method must do is described in its javadoc in AbstractQueuedSynchronizer
      /**
       * Attempts to acquire in exclusive mode. This method should query
       * if the state of the object permits it to be acquired in the
       * exclusive mode, and if so to acquire it.
       * ...
       */
      protected boolean tryAcquire(int arg) {
          throw new UnsupportedOperationException();
      }
    
    • implementation in NonfairSync does exactly that (and doesn't contain reentrancy functionality):
    /**
    * Acquire for non-reentrant cases after initialTryLock prescreen
    */
    protected final boolean tryAcquire(int acquires) {
        if (getState() == 0 && compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(Thread.currentThread());
            return true;
        }
        return false;
    }
    
rte
  • 11
  • 1
  • so what stops `NonfairSync::tryAcquire` to return `false`? it is a non-public class with a non-public method. – Eugene Jan 30 '21 at 04:17
  • From [user15110003](https://stackoverflow.com/users/15110003/user15110003): `tryAcquire()` is used in several places inside `AbstractQueuedSynchronizer`. In the place you found it would be fine if `tryAcquire()` did nothing and returned `false`. But in other places inside `AbstractQueuedSynchronizer` it would break `AbstractQueuedSynchronizer` if `tryAcquire()` didn't adhere its contract in javadoc for `AbstractQueuedSynchronizer::tryAcquire`. – dan1st Jan 30 '21 at 15:14
  • @dan1st right. I hope I missed it, but can you spot any code flows where `tryAcquire` is not followed by `acquire` in `AbstractQueuedSynchronizer`? I do understand obeying to the javadoc, but returning `false` would not be a violation of that. I mean the default implementation even throws an Exception. Besides the javadoc says : _Attempts to acquire in exclusive mode_, which `initialTryLock` already did via `if (compareAndSetState(0, 1)`. And btw you gotta tag me with `@` when posting a comment – Eugene Jan 31 '21 at 00:04