开发者

ArrayBlockingQueue: why signal conditions in InterruptedException blocks?

I have been looking at the source of ArrayBlockingQueue. I am trying to understand why conditions are signalled when catching InterruptedException's. Here is an example, notice the notFull.signal() call before the InterruptedException is rethrown. Why is this necessary? If there are 2 threads simultaneously invoking offer and one is interrupted, wouldn't the other thread then enter the critical section guarded by the lock and then see a count < items.length?

public boolean offer(E e, long timeout, TimeUnit unit)
    throws InterruptedException {

    if (e == null) throw new NullPointerException();
long nanos = unit.toNanos(timeout);
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        for (;;) {
            if (count != items.length) {
                insert(e);
                return true;
            }
            if (nanos <= 0)
                return false;
   开发者_如何学JAVA         try {
                nanos = notFull.awaitNanos(nanos);
            } catch (InterruptedException ie) {
                notFull.signal(); // propagate to non-interrupted thread
                throw ie;
            }
        }
    } finally {
        lock.unlock();
    }
}


It is entirely possible that a thread waiting on await will be signalled and interrupted, and that the interruption will take precedence. The following code demonstrates this:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SignalTest {
    public static void main(String... args) throws InterruptedException {
        for ( int i = 0; i < 2000; i++ ) {
            tryOnce();
        }
    }

    private static void tryOnce() throws InterruptedException {
        final Lock lock = new ReentrantLock();
        final Condition condition = lock.newCondition();

        Thread t = new Thread(new Runnable() {
            public void run() {

                try {
                    lock.lockInterruptibly();
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });

        t.start();

        Thread.sleep(1);
        lock.lock();
        condition.signal();
        t.interrupt();
        lock.unlock();
    }
}

For me, 2-10 of the 2000 attempts result in an InterruptedException, even though I'm signalling before I'm interrupting and doing both from the same thread.

Since this is a very real possibility, if the catch block did not propogate up the signal, it could result in a permanently waiting state, even though there's space available to add the new element. So instead the condition is signalled and if there is another waiting thread, it is woken up.

This is safe because after being woken up, the code will always make sure that the condition on which they were woken up (that the queue was not full) is actually true (in this case, if (count != items.length)...). The code will not assume that since it was woken up, the condition must be true.

Edit

Also, to aid your understanding, it's important to note that the for loop is not strictly a mutually exclusive section. If two threads call offer at the same time, the second will wait on the first to yield lock, that is true, but you must understand that calling notFull.await() releases the lock (the lock will be picked up again after await() returns). Therefore, you can have multiple threads blocked at the await() call of offer(), not simply blocked at lockInterruptably().

Since that's the case (there are multiple blocking await()s happening at the same time) if the interrupted thread silently ignored the signal then none of the other threads would be woken up in its place.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜