简体   繁体   中英

why is there a while loop in put() of LinkedBlockingQueue

public void put(E e) throws InterruptedException {
    if (e == null) throw new NullPointerException();
    int c = -1;
    Node<E> node = new Node<E>(e);
    final ReentrantLock putLock = this.putLock;
    final AtomicInteger count = this.count;
    putLock.lockInterruptibly();
    try {
        while (count.get() == capacity) {
            notFull.await();
        }
        enqueue(node);
        c = count.getAndIncrement();
        if (c + 1 < capacity)
            notFull.signal();
    } finally {
        putLock.unlock();
    }
    if (c == 0)
        signalNotEmpty();
}

why is there a while loop?

All the putting thread is shut out by putLock.

No thread can increase 'count' when the waiting thread is holding the putLock.

There is a fundamental property of await (which applies to the intrinsic locking via synchronized and using Object.wait as well), you have to understand:

When you invoke await , you are releasing the lock this Condition is associated with¹. There is no way around it, as otherwise, no-one could acquire the lock, make the condition fulfilled, and invoke signal on it.

When your waiting thread gets signaled, it does not get the lock back immediately. That would not be possible, as the thread which invoked signal still owns it. Instead, the receiver will try to re-acquire the lock, not much different to calling lockInterruptibly() .

But this thread is not necessarily the only thread trying to acquire the lock. It doesn't even have to be the first one. Another thread could have arrived at put before the signalling and waiting for the lock at lockInterruptibly() . So even if the lock was fair (which locks usually are not), the signaled thread had no precedence. Even if you gave signaled threads precedence, there could be multiple threads being signaled for different reasons.

So another thread arriving at put could get the lock before the signaled thread, find that there is space, and store the element without ever bothering with signals. Then, by the time the signaled thread acquired the lock, the condition is not fulfilled anymore. So a signaled thread can never rely on the validity of the condition just because it received a signal and therefore has to re-check the condition and invoke await again if not fulfilled.

This makes checking the condition in a loop the standard idiom of using await , as documented in the Condition interface , as well as Object.wait for the case of using the intrinsic monitor, just for completeness. In other words, this is not even specific to a particular API.

Since the condition has to be pre-checked and re-checked in a loop anyway, the specification even allows for spurious wakeups , the event of a thread returning from the wait operation without actually receiving a signal. This may simplify lock implementations of certain platforms, while not changing the way a lock has to be used.

¹ It's important to emphasize that when holding multiple locks, only the lock associated with the condition is released.

Loop's function(*) is blocking the thread that is called put method when LinkedBlockingQueue's capacity is full. When another thread call take (or poll) method, there will be space for new element in the queue and take method will signal the notFull Condition, and the waiting thread will be woken up and can put the item to the queue.

(*) Condition of the loop is for to guarantee that a spurious wakeup did not occured.

https://en.wikipedia.org/wiki/Spurious_wakeup

@Holder's answer is correct but I'd like to add more details about the following code and question.

putLock.lockInterruptibly();
try {
    while (count.get() == capacity) {
        notFull.await();
    }
    ...

why is there a while loop? All the putting thread is shut out by putLock.

The while loop is a critical part of this code pattern that ensures that when the thread is awoken from a signal on notFull , it makes sure that another thread didn't get there first and re-fill the buffer.

One thing that it is important to realize that the notFull is defined as a condition on putLock :

private final Condition notFull = putLock.newCondition();

When the thread calls notFull.await() it will unlock putLock which means that multiple threads could be running notFull.await() at the same time. A thread will only attempt to reacquire the lock after notFull.signal() (or signalAll() ) is called.

The race condition happens if thread-A is BLOCKED trying to acquire putLock and thread-B is WAITING on notFull . If a thread-C removes something from the queue and signals notFull , thread-B will be taken out of the waiting-queue and put into the blocked-queue on putLock but unfortunately, it will be behind thread-A which was already blocked. So once putLock is unlocked, thread-A will acquire putLock and put something into the queue, filling it again. When thread-B finally acquires putLock , it needs to test again to see if there is still space available before putting (and overflowing) the queue. That's why the while is necessary.

A secondary reason for the while loop, as @Holder also mentioned, is to protect against spurious wakeups which can happen under certain thread architectures when a condition gets signaled artificially. For example, under some architectures, a signal on any condition signals all conditions because of OS limitations.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM