Reentrant lock implementation detail

Viewed 233

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?

2 Answers

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.

  • 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;
    }
    
Related