Java 在什么情况下BlockingQueue.take会引发中断异常?
假设有一个线程使用另一个线程生成的项目。其运行方法如下,inQueue是BlockingQueueJava 在什么情况下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; } } 此外,另一个线程将通过中
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