Java 在什么情况下BlockingQueue.take会引发中断异常?

Java 在什么情况下BlockingQueue.take会引发中断异常?,java,concurrency,interrupted-exception,Java,Concurrency,Interrupted Exception,假设有一个线程使用另一个线程生成的项目。其运行方法如下,inQueue是BlockingQueue boolean shutdown = false; while (!shutdown) { try { WorkItem w = inQueue.take(); w.consume(); } catch (InterruptedException e) { shutdown = true; } } 此外,另一个线程将通过中

假设有一个线程使用另一个线程生成的项目。其运行方法如下,inQueue是BlockingQueue

boolean shutdown = false;
while (!shutdown) {
    try {
        WorkItem w = inQueue.take();
        w.consume();
    } catch (InterruptedException e) { 
        shutdown = true;
    }
}

此外,另一个线程将通过中断这个正在运行的线程来表示没有更多的工作项。如果不需要阻止以检索下一个工作项,则take()将抛出中断的异常。i、 e.如果生产商发出信号,表示已完成填充工作队列,是否可能意外地将某些项目留在队列中或错过中断?

发出阻塞队列终止信号的一个好方法是向队列中提交一个“有毒”值,指示已发生关机。这可确保遵守队列的预期行为。如果要清除队列,调用Thread.interupt()可能不是一个好主意

要提供一些代码:

boolean shutdown = false;
while (!shutdown) {
    try {
        WorkItem w = inQueue.take();
        if (w == QUEUE_IS_DEAD)
          shutdown = true;
        else
          w.consume();
    } catch (InterruptedException e) { 
        // possibly submit QUEUE_IS_DEAD to the queue
    }
}

根据,如果在等待时被中断,
take()
方法将抛出
InterruptedException

java.concurrency.utils包是由并发编程中一些最优秀的人设计和实现的。此外,中断线程作为终止线程的一种手段,在他们的书《Java并发实践》中得到了明确的认可。因此,如果有任何项目由于中断而留在队列中,我会非常惊讶。

我想知道同样的事情,并且阅读javadoc,因为我相信只有在获取了队列中的所有项目之后,它才会抛出中断的异常,因为如果队列中有项目,它就不必“等待”。 但我做了一个小测试:

package se.fkykko.slask;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.atomic.AtomicLong;

public class BlockingQueueTakeTest {

public static void main(String[] args) throws Exception {
    Runner t = new Runner();
    Thread t1 = new Thread(t);
    for (int i = 0; i < 50; i++) {
        t.queue.add(i);
    }
    System.out.println(("Number of items in queue: " + t.queue.size()));
    t1.start();
    Thread.sleep(1000);
    t1.interrupt();
    t1.join();
    System.out.println(("Number of items in queue: " + t.queue.size()));
    System.out.println(("Joined t1. Finished"));

}

private static final class Runner implements Runnable {
    BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(100);
    AtomicLong m_count = new AtomicLong(0);

    @Override
    public void run() {
        try {
            while (true) {
                queue.take();
                System.out.println("Took item " + m_count.incrementAndGet());
                final long start = System.currentTimeMillis();
                while ((System.currentTimeMillis() - start) < 100) {
                    Thread.yield(); //Spin wait
                }
            }
        }
        catch (InterruptedException ex) {
            System.out.println("Interrupted. Count: " + m_count.get());
        }
    }
}

}
包se.fkykko.slask;
导入java.util.concurrent.ArrayBlockingQueue;
导入java.util.concurrent.BlockingQueue;
导入java.util.concurrent.AtomicLong;
公共类阻塞队列测试{
公共静态void main(字符串[]args)引发异常{
流道t=新流道();
螺纹t1=新螺纹(t);
对于(int i=0;i<50;i++){
t、 加入(i);
}
System.out.println(((“队列中的项目数:+t.queue.size()));
t1.start();
睡眠(1000);
t1.中断();
t1.join();
System.out.println(((“队列中的项目数:+t.queue.size()));
System.out.println((“连接t1.Finished”);
}
私有静态最终类运行器实现Runnable{
BlockingQueue=new ArrayBlockingQueue(100);
原子长m_计数=新的原子长(0);
@凌驾
公开募捐{
试一试{
while(true){
queue.take();
System.out.println(“taked item”+m_count.incrementAndGet());
最终长启动=System.currentTimeMillis();
而((System.currentTimeMillis()-start)<100){
Thread.yield();//自旋等待
}
}
}
捕获(中断异常例外){
System.out.println(“Interrupted.Count:+m_Count.get());
}
}
}
}
运行程序将获取10-11个项目,然后完成,即take()将抛出InterruptedException,即使队列中仍有项目


小结:改用毒药丸方法,那么您就可以完全控制队列中剩余的数量。

如果您使用
ExecutorService::execute(Runnable)
启动线程,则通常不能从外部代码中断
ExecutorService
的线程,因为外部代码没有对每个正在运行的线程的
线程
对象的引用(如果需要
ExecutorService::execute
,请参阅此答案的末尾以获取解决方案)。但是,如果改为使用
ExecutorService::submit(Callable)
提交作业,则会返回一个
Future
,一旦
Callable::call()
开始执行,它会在内部保留对正在运行的线程的引用。此线程可以通过调用
Future::cancel(true)
中断。因此,检查当前线程中断状态的
可调用
中(或由其调用)的任何代码都可以通过
未来
引用中断。这包括
BlockingQueue::take()
,即使被阻止,它也会响应线程中断。(JRE阻塞方法通常会在阻塞时被中断时唤醒,意识到它们已被中断,并抛出
中断异常

总之:
Future::cancel()
Future::cancel(true)
都会取消将来的工作,而
Future::cancel(true)
也会中断正在进行的工作(只要正在进行的工作响应线程中断)。两次
cancel
调用都不会影响已经成功完成的工作

请注意,一旦线程被取消中断,线程中将抛出一个
InterruptException
(例如,在这种情况下,通过
BlockingQueue::take()
)。但是,下次调用
Future::get()
时,成功取消的
Future
上的
CancellationException
将在主线程中抛出一个
Future
(即在完成之前取消的
Future
)。这与您通常期望的不同:如果未取消的
可调用的
抛出
中断异常
,则对
Future::get()
的下一次调用将抛出
InterruptedException
,但如果取消的
可调用的
抛出
InterruptedException
,则对
Future::get()的下一次调用
将通过
取消异常

这里有一个例子说明了这一点:

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;

public class Test {
    public static void main(String[] args) throws Exception {
        // Start Executor with 4 threads
        int numThreads = 4;
        ThreadPoolExecutor executor = (ThreadPoolExecutor) Executors.newFixedThreadPool(numThreads);
        try {
            // Set up BlockingQueue for inputs, and List<Future> for outputs
            BlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
            List<Future<String>> futures = new ArrayList<>(numThreads);
            for (int i = 0; i < numThreads; i++) {
                int threadIdx = i;
                futures.add(executor.submit(new Callable<String>() {
                    @Override
                    public String call() throws Exception {
                        try {
                            // Get an input from the queue (blocking)
                            int val = queue.take();
                            return "Thread " + threadIdx + " got value " + val;
                        } catch (InterruptedException e) {
                            // Thrown once Future::cancel(true) is called
                            System.out.println("Thread " + threadIdx + " got interrupted");
                            // This value is returned to the Future, but can never
                            // be read, since the caller will get a CancellationException
                            return "Thread " + threadIdx + " got no value";
                        }
                    }
                }));
            }

            // Enqueue (numThreads - 1) values into the queue, so that one thread blocks
            for (int i = 0; i < numThreads - 1; i++) {
                queue.add(100 + i);
            }

            // Cancel all futures
            for (int i = 0; i < futures.size(); i++) {
                Future<String> future = futures.get(i);
                // Cancel the Future -- this doesn't throw an exception until
                // the get() method is called
                future.cancel(/* mayInterruptIfRunning = */ true);
                try {
                    System.out.println(future.get());
                } catch (CancellationException e) {
                    System.out.println("Future " + i + " was cancelled");
                }
            }
        } finally {
            // Terminate main after all threads have shut down (this call does not block,
            // so main will exit before the threads stop running)
            executor.shutdown();
        }
    }
}
这表明线程2和线程3在调用
Future::cancel()
之前已完成。线程1被取消,因此在内部引发了InterruptedException,在外部引发了CancellationException。线程0在停止之前已被取消
Future 1 was cancelled
Future 0 was cancelled
Thread 2 got value 100
Thread 3 got value 101
Thread 1 got interrupted