1

Two sentences in the JavaDoc for AbstractQueuedSynchronizer are in my opinion unclear/misleading, and therefore I would like to clarify such issues.

The first sentence states:

Even though this class is based on an internal FIFO queue, it does not automatically enforce FIFO acquisition policies.

What is exactly meant with the sentence above? Looking up the source code for the class itself (which has several methods marked as final), I am currently seeing only one case where FIFO could not be enforced, that is when tryAcquire returns true. When a tryAcquire returns false and the caller thread must be parked, I can not find any way to bypass FIFO. In case my assumption is correct, should not the documentation be made more clear?

The second sentence states:

Because checks in acquire are invoked before enqueuing, a newly acquiring thread may barge ahead of others that are blocked and queued. However, you can, if desired, define tryAcquire and/or tryAcquireShared to disable barging by internally invoking one or more of the inspection methods, thereby providing a fair FIFO acquisition order.

What is exactly meant here with barging? Is the documentation talking again about the case where the acquiring thread is not parked?

My currently understanding is as following: as long as the acquiring thread is not parked, strategies other than FIFO are possible. In case the acquiring thread must be parked, FIFO will be used.

Do you agree with my statements or am I getting something wrong? Does it make sense to open a BUG for Oracle to fix it?

Ciaccia
  • 382
  • 3
  • 14

1 Answers1

1

Take a look at the source code of ReentrantLock to see how that's implemented in practice.
NonfairSync:

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

FairSync:

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

As you can see, FairSync.tryAcquire() additionally checks for !hasQueuedPredecessors(), exactly as the AbstractQueuedSynchronizer's javadoc recommends:

Because checks in acquire are invoked before enqueuing, a newly acquiring thread may barge ahead of others that are blocked and queued. However, you can, if desired, define tryAcquire and/or tryAcquireShared to disable barging by internally invoking one or more of the inspection methods, thereby providing a fair FIFO acquisition order. In particular, most fair synchronizers can define tryAcquire to return false if hasQueuedPredecessors() (a method specifically designed to be used by fair synchronizers) returns true. Other variations are possible.

In simple words, the following case demonstrates the difference:

  1. the current lock owner releases the lock, and during the release the longest-waiting thread from FIFO queue is unparked.
  2. before the unparked thread resumes execution and acquires the lock, another thread tries to acquire this lock too.
    • FairSync parks the new thread and puts it at the end of the FIFO queue: here all threads acquire the lock in FIFO order
    • NonfairSync allows the new thread to acquire the lock: here we lose fairness, but gain higher performance