Hi Archie and Viktor,

I apologize for the delay in my response.



After further consideration, I believe my understanding aligns more closely 
with Interpretation B.

To clarify my understanding of Interpretation B, threads waiting on a Condition 
(linked through ConditionNode in ConditionObject) receive signals in the order 
in which they started waiting, but receiving a signal doesn't guarantee 
immediate reacquisition of the ReentrantLock. Instead, as far as I understand, 
the signal simply enqueues the thread at the end of the AQS 
queue(ReentrantLock).



As a result, threads that received a signal from ConditionObject do not have 
priority over the threads already waiting in the AQS queue and are simply added 
to the end of that queue.

From what I understand, the difference between NonFairSync and FairSync lies in 
whether a thread can acquire the lock when there are other threads already 
waiting in the AQS queue, rather than anything related to the threads signaled 
from a Condition.



Additionally, as I understand it, the enqueue() and doSignal() methods are 
declared final in AQS, and both Syncimplementations use the AQS methods 
directly.



Therefore, I wanted to highlight that this isn't necessarily an issue with AQS 
or FairSync. Instead, I suspect that the behavior we're seeing could arise from 
the fact that threads signaled in a BlockingQueue implementation may not 
execute immediately, which is a characteristic of the implementation.



However, there may be aspects I’m misunderstanding, and I would appreciate any 
corrections or clarifications if that’s the case.



Thank you very much for your time and understanding.

Best regards,

Kim Minju


> 2024. 9. 8. 오후 11:39, Archie Cobbs <archie.co...@gmail.com> 작성:
> 
> Hi Kim,
> 
> Thanks for the details.
> 
> So to summarize:
> Kim is saying that "Interpretation B" is how it actually works.
> Viktor is saying that "Interpretation A" is how it actually works.
> Do I have that right?
> 
> -Archie
> 
> P.S. Viktor: my apologies for misspelling your name before
> 
> 
> On Sat, Sep 7, 2024 at 8:04 PM 김민주 <miiiinj...@gmail.com 
> <mailto:miiiinj...@gmail.com>> wrote:
>> Hi Arhice,
>> 
>> 
>> 
>> First of all, I want to apologize if I may not have explained things clearly 
>> since English is not my first language. I’m really sorry about that.
>> 
>> 
>> 
>> Even so, I deeply appreciate your response and taking the time to reply.
>> 
>> 
>> 
>> First, I would like to confirm whether my understanding is correct.
>> 
>> From what I know, ReentrantLock is based on AQS, and internally, threads are 
>> queued by being linked as Node.
>> 
>> When ReentrantLock.newCondition is called, a ConditionObject is created. 
>> When Condition::await is called, the thread waits based on a ConditionNode, 
>> and the AQS head is replaced with AQS::signalNext, allowing the lock to be 
>> released. At this point, I understand that the node disappears from the AQS 
>> queue and starts waiting within the ConditionNode of the ConditionObject.
>> 
>> As a result, I understand that the waiting places for ReentrantLock and 
>> Condition are different.
>> 
>> To summarize, when Condition::await() is called, the node that was 
>> previously waiting in AQS receives the lock, disappears from the AQS queue, 
>> and starts waiting within the ConditionNode of the ConditionObject.
>> 
>> Additionally, from what I understand, in Condition::doSignal, the first 
>> ConditionNode is removed, and then AQS::enqueue adds it to the tail of AQS.
>> 
>> public class ConditionObject implements Condition, java.io.Serializable {
>> 
>> private void doSignal(ConditionNode first, boolean all) {
>>     while (first != null) {
>>         ConditionNode next = first.nextWaiter;
>>         if ((firstWaiter = next) == null)
>>             lastWaiter = null;
>>         if ((first.getAndUnsetStatus(COND) & COND) != 0) {
>>             enqueue(first);
>>             if (!all)
>>                 break;
>>         }
>>         first = next;
>>     }
>> }
>> 
>> When enqueue is called:
>> 
>> public abstract class AbstractQueuedSynchronizer
>>     extends AbstractOwnableSynchronizer
>> 
>>     /*
>>      * Tail of the wait queue. After initialization, modified only via 
>> casTail.
>>      */
>>     private transient volatile Node tail;
>> 
>>       final void enqueue(Node node) {
>>          if (node != null) {
>>              for (;;) {
>>                  Node t = tail;
>>                  node.setPrevRelaxed(t);        // avoid unnecessary fence
>>                  if (t == null)                 // initialize
>>                      tryInitializeHead();
>>                  else if (casTail(t, node)) {
>>                      t.next = node;
>>                      if (t.status < 0)          // wake up to clean link
>>                          LockSupport.unpark(node.waiter);
>>                      break;
>>                  }
>>              }
>>          }
>> }
>> 
>> From my understanding, this attaches the node to the tail of AQS.
>> 
>> To elaborate further on the situation:
>> 
>> Threads T1 to T10 are waiting on Condition::await() because the queue is 
>> full.
>> 
>> (At this point, T1 to T10 are linked through ConditionNode.) [The AQS queue 
>> is empty, while ConditionNode holds T1 to T10.]
>> 
>> T11 calls take() and holds the lock via lock.lockInterruptibly().
>> 
>> (Since no threads are waiting in the AQS queue, T11 will acquire the lock 
>> immediately.)
>> 
>> [Now, AQS holds T11 at its head, and ConditionNode holds T1 to T10.]
>> 
>> T12 calls queue.put() and enters the wait queue for 
>> lock.lockInterruptibly(). (Since T11 is holding the lock with take(), T12 
>> will be queued behind it in AQS.)
>> 
>> [Now, AQS holds T11 and T12, while ConditionNode holds T1 to T10.]
>> 
>> T11 reduces the count and sends a signal, then releases the lock.
>> 
>> T1 receives the signal and moves to the lock queue. Since ReentrantLock is 
>> in fair mode,
>> 
>> (When T11 sends the signal, T1, the first thread linked in ConditionNode, 
>> will be enqueued via AQS::enqueue. Now, AQS holds T11, T12, and T1, while 
>> ConditionNode holds T2 to T10.)
>> 
>> T11 releases the lock and wakes up T12.
>> 
>> [Now, AQS holds T12 and T1, while ConditionNode holds T2 to T10.]
>> 
>> T12 acquires the lock and proceeds to enqueue in ArrayBlockingQueue without 
>> being blocked by while(count==length).
>> 
>> T12 releases the lock, and the next node in AQS is unparked.
>> 
>> [Now, AQS holds T1, while ConditionNode holds T2 to T10.]
>> 
>> T1, having reacquired the lock after Condition::await(), fails to exit the 
>> while loop and waits again.
>> 
>> [Now, ConditionNode holds T1 and T2 to T10.]
>> 
>> 
>> 
>> This is how I currently understand the situation.
>> 
>> If there are any mistakes in my understanding, I would greatly appreciate 
>> your clarification.
>> 
>> Best Regards,
>> 
>>  Kim Minju
>> 
>> 
>>> 2024. 9. 8. 오전 3:34, Archie Cobbs <archie.co...@gmail.com 
>>> <mailto:archie.co...@gmail.com>> 작성:
>>> 
>>> Hi Kim,
>>> 
>>> On Sat, Sep 7, 2024 at 10:36 AM 김민주 <miiiinj...@gmail.com 
>>> <mailto:miiiinj...@gmail.com>> wrote:
>>>> Here's a clearer outline of the scenario:
>>>> 
>>>> Threads T1 to T10 are waiting on Condition::await() because the queue is 
>>>> full.
>>>> T11 calls take() and holds the lock via lock.lockInterruptibly().
>>>> T12 calls queue.put() and enters the wait queue for 
>>>> lock.lockInterruptibly(). (As I understand, the wait queue for 
>>>> ReentrantLock and Condition are separate.)
>>>> T11 reduces the count and sends a signal, then releases the lock.
>>>> T1 receives the signal and moves to the lock queue. Since the 
>>>> ReentrantLock is in fair mode, T12 (which was already waiting) gets 
>>>> priority, and T1 will acquire the lock later.
>>>> T12 acquires the lock and successfully enqueues.
>>> From one reading of the Javadoc, your step #5 ("T12 gets priority") is not 
>>> supposed to happen that way. Instead, one of T1 through T10 should be 
>>> guaranteed to acquire the lock.
>>> 
>>> Here it is again (from ReentrantLock.newCondition()):
>>> 
>>>> The ordering of lock reacquisition for threads returning from waiting 
>>>> methods is the same as for threads initially acquiring the lock, which is 
>>>> in the default case not specified, but for fair locks favors those threads 
>>>> that have been waiting the longest.
>>> 
>>> 
>>> But part of the problem here is that this documentation is ambiguous.
>>> 
>>> The ambiguity is: are ALL threads trying to acquire the lock, whether on an 
>>> initial attempt or after a condition wakeup, ordered for fairness together 
>>> in one big pool? → In this case step #5 can't happen. Call this 
>>> Interpretation A.
>>> 
>>> Or is this merely saying that threads waiting on a condition reacquire the 
>>> lock based on when they started waiting on the condition, but there are no 
>>> ordering guarantees between those threads and any other unrelated threads 
>>> trying to acquire the lock? → In this case step #5 can happen. Call this 
>>> Interpretation B.
>>> 
>>> So I think we first need to clarify which interpretation is correct here, A 
>>> or B.
>>> 
>>> On that point, Victor said this:
>>> 
>>>> I've re-read ReentrantLock and AQS, and from my understanding on the logic 
>>>> the Condition's place in the wait queue should be maintained, which means 
>>>> that T3 shouldn't be able to "barge".
>>> 
>>> 
>>> So it sounds like Victor is confirming interpretation A. Victor do you 
>>> agree?
>>> 
>>> If so, then it seems like we need to do two things:
>>> 
>>> 1. File a Jira ticket to clarify the Javadoc, e.g. to say something like 
>>> this:
>>> 
>>>> The ordering of lock reacquisition for threads returning from waiting 
>>>> methods is the same as for threads initially acquiring the lock, which is 
>>>> in the default case not specified, but for fair locks favors those threads 
>>>> that have been waiting the longest. In the latter case, the ordering 
>>>> consideration includes all threads attempting to acquire the lock, 
>>>> regardless of whether or not they were previously blocked on the condition.
>>> 
>>> 
>>> 2. Understand why Kim's updated test case is still failing (it must be 
>>> either a bug in the test or a bug in ArrayBlockingQueue).
>>> 
>>> -Archie
>>> 
>>> --
>>> Archie L. Cobbs
>> 
> 
> 
> --
> Archie L. Cobbs

Reply via email to