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;
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
awaitwill be signalled and interrupted, and that the interruption will take precedence. The following code demonstrates this: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 callingnotFull.await()releases the lock (the lock will be picked up again afterawait()returns). Therefore, you can have multiple threads blocked at theawait()call ofoffer(), not simply blocked atlockInterruptably().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.