让我们假设我有一个消耗另一个线程产生的项的线程。其运行方法如下,其中inQueue为BlockingQueue
boolean shutdown = false;
while (!shutdown) {
try {
WorkItem w = inQueue.take();
w.consume();
} catch (InterruptedException e) {
shutdown = true;
}
}
此外,一个不同的线程将通过中断此运行的线程来发出没有更多工作项的信号。如果不需要阻塞来检索下一个工作项,take()会抛出一个中断的异常。即如果生产者发出信号说它已完成填充工作队列,是否可能意外地将一些物品留在inQueue中或错过中断?
答案 0 :(得分:4)
发出阻塞队列终止信号的好方法是向队列中提交一个“毒药”值,表示已发生关机。这可确保遵循队列的预期行为。如果你关心清理队列,调用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
}
}
答案 1 :(得分:3)
根据javadoc,take()
方法会在等待时被中断时抛出InterruptedException
。
答案 2 :(得分:2)
我想知道同样的事情并且阅读take()的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());
}
}
}
}
跑步者将获得10-11个项目然后完成,即使队列中仍有项目,take()将抛出InterruptedException。
总结:使用毒丸方法代替,然后您可以完全控制队列中剩余的数量。
答案 3 :(得分:0)
如果您使用ExecutorService
启动线程,则通常无法从外部代码中断ExecutorService::execute(Runnable)
的线程,因为外部代码没有对Thread
的引用每个正在运行的线程的对象(但是,如果需要ExecutorService::execute
,请参见此答案的结尾以获取解决方案)。但是,如果改为使用ExecutorService::submit(Callable<T>)
提交作业,则会返回一个Future<T>
,一旦Callable::call()
开始执行,它会在内部保留对正在运行的线程的引用。可以通过调用Future::cancel(true)
来中断该线程。因此,Callable
内(或由其调用)检查当前线程的中断状态的任何代码都可以通过Future
引用来中断。这包括BlockingQueue::take()
,即使被阻止,它们也将响应线程中断。 (JRE阻止方法通常会在被阻止时被中断唤醒,意识到它们已被中断,并抛出InterruptedException
。)
总结:Future::cancel()
和Future::cancel(true)
都取消将来的工作,而Future::cancel(true)
也中断正在进行的工作(只要正在进行的工作会响应线程中断)。这两个cancel
调用都不会影响已经成功完成的工作。
请注意,一旦某个线程被取消中断,则在该线程中将抛出一个InterruptException
(例如,在这种情况下为BlockingQueue::take()
)。但是,下一次在成功取消的CancellationException
(即在完成之前被取消的Future::get()
上调用Future
时,您将Future
抛回到主线程中)。这与您通常期望的情况不同:如果未取消的Callable
抛出InterruptedException
,则对Future::get()
的下一次调用将抛出InterruptedException
,但是如果取消了{{ 1}}抛出Callable
,下一次对InterruptedException
的调用将通过Future::get()
。
下面是一个说明这一点的例子:
CancellationException
每次运行此命令时,输出都会不同,但这是一次运行:
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 1 was cancelled
Future 0 was cancelled
Thread 2 got value 100
Thread 3 got value 101
Thread 1 got interrupted
之前完成。线程1被取消,因此内部Future::cancel()
被引发,而外部InterruptedException
被引发。线程0在开始运行之前被取消。 (请注意,线程索引通常不会与CancellationException
索引相关,因此Future
可能对应于线程0或线程1被取消,而Future 0 was cancelled
则相同。)
高级:使用Future 1 was cancelled
(不返回Executor::execute
引用)而不是Future
来达到相同效果的一种方法是创建一个Executor::submit
和自定义ThreadPoolExecutor
,并让您的ThreadFactory
在创建的每个线程的并发集合(例如,并发队列)中记录引用。然后,要取消所有线程,只需在所有先前创建的线程上调用ThreadFactory
。但是,您将需要处理在中断现有线程时可能会创建新线程的竞争条件。要解决此问题,请设置Thread::interrupt()
可见的AtomicBoolean
标志,告诉它不要创建更多线程,然后在设置后取消现有线程。
答案 4 :(得分:-1)
java.concurrency.utils包是由并发编程中的一些最优秀的人才设计和实现的。此外,中断线程作为终止它们的手段也得到了他们的书“Java Concurrency in Practice”的明确支持。因此,如果由于中断而将任何项目留在队列中,我会感到非常惊讶。