ArrayBlockingQueue:为什么InterruptedException中的信号条件会阻塞?

时间:2011-07-10 16:01:15

标签: java multithreading

我一直在关注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();
    }
}

1 个答案:

答案 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())如果被中断的线程默默地忽略了该信号,那么其他任何线程都不会被唤醒。