我一直在关注ArrayBlockingQueue
的来源。我试图理解为什么在捕捉InterruptedException
时发出条件信号。以下是一个示例,请注意notFull.signal()
之前的InterruptedException
调用。为什么这有必要?如果有2个线程同时调用offer
并且其中一个被中断,则另一个线程不会进入由该锁保护的临界区,然后查看计数< 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();
}
}
答案 0 :(得分:3)
等待await
的线程完全有可能发出和信号,并且中断将优先。以下代码演示了这一点:
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();
}
}
对我来说,2000次尝试中有2到10次会产生InterruptedException
,即使我在中断并在同一个线程中执行这两项操作之前发出信号。
由于这是一个非常现实的可能性,如果catch块没有传播signal
,它可能会导致永久等待状态,即使有可用空间来添加新元素。所以相反,条件被发出信号,如果有另一个等待线程,它就会被唤醒。
这是安全的,因为在被唤醒之后,代码将始终确保它们被唤醒的条件(队列未满)实际上是真的(在这种情况下, if (count != items.length)...
)。代码不会假设因为它被唤醒了,所以条件必须为真。
另外,为了帮助您理解,重要的是要注意for循环不是严格的互斥部分。如果两个线程同时调用offer,则第二个线程将在第一个线程上等待lock
,这是真的,但您必须了解调用notFull.await()
释放锁 (await()
返回后,锁定将再次被选中)。因此,您可以在await()
offer()
来电时阻止多个线程,而不仅仅是lockInterruptably()
阻止。
由于这种情况(同时发生多个阻塞await()
)如果被中断的线程默默地忽略了该信号,那么其他任何线程都不会被唤醒。