我在一个非常简单的生产者 - 消费者场景中使用 java.util.concurrent.BlockingQueue 。例如。这个伪代码描述了消费者部分:
class QueueConsumer implements Runnable {
@Override
public void run() {
while(true)
{
try {
ComplexObject complexObject = myBlockingQueue.take();
//do something with the complex object
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
到目前为止一切顺利。在阻塞队列的javadoc中,我读到:
BlockingQueue本质上不是 支持任何一种“关闭”或 “关闭”操作表明 不会再添加任何项目。需求 并且这些特征的使用倾向于 实现有关。例如, 一个共同的策略是生产者 插入特殊的流末尾或毒药 对象,被解释 因此,当消费者采取时。
不幸的是,由于使用的泛型和ComplexObject的性质,将“毒物对象”推入队列并非易事。所以这种“共同策略”在我的场景中并不是很方便。
我的问题是:我可以用什么其他好的策略/模式来“关闭”队列?
谢谢!
答案 0 :(得分:19)
如果你有一个消费者线程的句柄,你可以打断它。使用您提供的代码,这将杀死消费者。我不希望制片人有这个;它可能不得不以某种方式回调程序控制器让它知道它已经完成。然后控制器会中断消费者线程。
在遵守中断之前,您始终可以完成工作。例如:
class QueueConsumer implements Runnable {
@Override
public void run() {
while(!(Thread.currentThread().isInterrupted())) {
try {
final ComplexObject complexObject = myBlockingQueue.take();
this.process(complexObject);
} catch (InterruptedException e) {
// Set interrupted flag.
Thread.currentThread().interrupt();
}
}
// Thread is getting ready to die, but first,
// drain remaining elements on the queue and process them.
final LinkedList<ComplexObject> remainingObjects;
myBlockingQueue.drainTo(remainingObjects);
for(ComplexObject complexObject : remainingObjects) {
this.process(complexObject);
}
}
private void process(final ComplexObject complexObject) {
// Do something with the complex object.
}
}
我实际上更愿意以某种方式毒害队列。如果你想杀死线程,请让线程自杀。
(很高兴看到有人正确处理InterruptedException
。)
这里似乎存在一些关于处理中断的争论。首先,我希望大家阅读这篇文章:http://www.ibm.com/developerworks/java/library/j-jtp05236.html
现在,在理解没有人真正阅读过的情况下,这就是交易。如果一个线程在中断时当前正在阻塞,它将只接收InterruptedException
。在这种情况下,Thread.interrupted()
将返回false
。如果它没有阻止,则不会收到此异常,而Thread.interrupted()
将返回true
。因此,无论如何,您的循环保护应该绝对检查Thread.interrupted()
,否则可能会错失线程中断。
所以,既然你正在检查Thread.interrupted()
,无论如何,你被迫抓住InterruptedException
(即使你没被强迫也应该处理它),你现在有两个代码区域处理相同的事件,线程中断。处理此问题的一种方法是将它们规范化为一个条件,这意味着布尔状态检查可以抛出异常,或者异常可以设置布尔状态。我选择了后者。
编辑:请注意,静态Thread#interrupted方法清除当前线程的中断状态。
答案 1 :(得分:11)
使这个变得简单的另一个想法是:
class ComplexObject implements QueueableComplexObject
{
/* the meat of your complex object is here as before, just need to
* add the following line and the "implements" clause above
*/
@Override public ComplexObject asComplexObject() { return this; }
}
enum NullComplexObject implements QueueableComplexObject
{
INSTANCE;
@Override public ComplexObject asComplexObject() { return null; }
}
interface QueueableComplexObject
{
public ComplexObject asComplexObject();
}
然后使用BlockingQueue<QueueableComplexObject>
作为队列。如果您希望结束队列的处理,请执行queue.offer(NullComplexObject.INSTANCE)
。在消费者方面,做
boolean ok = true;
while (ok)
{
ComplexObject obj = queue.take().asComplexObject();
if (obj == null)
ok = false;
else
process(obj);
}
/* interrupt handling elided: implement this as you see fit,
* depending on whether you watch to swallow interrupts or propagate them
* as in your original post
*/
不需要instanceof
,并且您不必构建假的ComplexObject
,这可能是昂贵/困难的,具体取决于其实现。
答案 2 :(得分:9)
另一种方法是使用ExecutorService
包装您正在进行的处理,并让ExecutorService
本身控制是否将作业添加到队列中。
基本上,你利用ExecutorService.shutdown()
,当被叫时不允许执行者处理任何其他任务。
我不确定您当前是如何向示例中的QueueConsumer
提交任务的。我假设您有某种submit()
方法,并在示例中使用了类似的方法。
import java.util.concurrent.*;
class QueueConsumer {
private final ExecutorService executor = Executors.newSingleThreadExecutor();
public void shutdown() {
executor.shutdown(); // gracefully shuts down the executor
}
// 'Result' is a class you'll have to write yourself, if you want.
// If you don't need to provide a result, you can just Runnable
// instead of Callable.
public Future<Result> submit(final ComplexObject complexObject) {
if(executor.isShutdown()) {
// handle submitted tasks after the executor has been told to shutdown
}
return executor.submit(new Callable<Result>() {
@Override
public Result call() {
return process(complexObject);
}
});
}
private Result process(final ComplexObject complexObject) {
// Do something with the complex object.
}
}
这个例子只是java.util.concurrent
包提供的一个袖口的例子;可能会对它进行一些优化(例如,QueueConsumer
因为它自己的类可能甚至不是必需的;你可以只为提交任务的生产者提供ExecutorService
。 / p>
挖掘java.util.concurrent
包(从上面的一些链接开始)。你可能会发现它为你想要做的事情提供了很多很好的选择,你甚至不必担心规范工作队列。
答案 3 :(得分:6)
制作毒物对象的另一种可能性:使其成为该类的特定实例。这样,您就不必捣乱子类型或搞砸通用。
缺点:如果生产者和消费者之间存在某种序列化障碍,这将无效。
public class ComplexObject
{
public static final POISON_INSTANCE = new ComplexObject();
public ComplexObject(whatever arguments) {
}
// Empty constructor for creating poison instance.
private ComplexObject() {
}
}
class QueueConsumer implements Runnable {
@Override
public void run() {
while(!(Thread.currentThread().interrupted())) {
try {
final ComplexObject complexObject = myBlockingQueue.take();
if (complexObject == ComplexObject.POISON_INSTANCE)
return;
// Process complex object.
} catch (InterruptedException e) {
// Set interrupted flag.
Thread.currentThread().interrupt();
}
}
}
}
答案 4 :(得分:3)
是否可以扩展ComplexObject并模拟非平凡的创建功能?基本上你最终会得到一个shell对象,但你可以做instance of
来查看是否是队列对象的结束。
答案 5 :(得分:3)
您可以将通用对象包装到数据对象中。在此数据对象上,您可以添加其他数据,例如毒物对象状态。 dataobject是一个包含2个字段的类。 T complexObject;
和boolean poison;
。
您的使用者从队列中获取数据对象。如果返回了一个有害对象,则关闭使用者,否则你打开泛型并调用'process(complexObject)'。
我正在使用java.util.concurrent.LinkedBlockingDeque<E>
,以便您可以在队列末尾添加对象并从前面获取它们。这样你的对象将按顺序处理,但更重要的是,在你遇到毒物对象后关闭队列是安全的。
为了支持多个使用者,我在遇到它时将毒物对象添加回队列。
public final class Data<T> {
private boolean poison = false;
private T complexObject;
public Data() {
this.poison = true;
}
public Data(T complexObject) {
this.complexObject = complexObject;
}
public boolean isPoison() {
return poison;
}
public T getComplexObject() {
return complexObject;
}
}
public class Consumer <T> implements Runnable {
@Override
public final void run() {
Data<T> data;
try {
while (!(data = queue.takeFirst()).isPoison()) {
process(data.getComplexObject());
}
} catch (final InterruptedException e) {
Thread.currentThread().interrupt();
return;
}
// add the poison object back so other consumers can stop too.
queue.addLast(line);
}
}
答案 6 :(得分:1)
对我来说,实现一个近似的BlockingQueue
:
import java.util.concurrent.BlockingQueue;
public interface CloseableBlockingQueue<E> extends BlockingQueue<E> {
/** Returns <tt>true</tt> if this queue is closed, <tt>false</tt> otherwise. */
public boolean isClosed();
/** Closes this queue; elements cannot be added to a closed queue. **/
public void close();
}
使用以下行为来实现这一点非常简单(参见methods summary table):
插入强>:
Throws exception,Special value:
表现得像一个完整的Queue
,来电者有责任测试isClosed()
。
关闭时会抛出IllegalStateException
。
如果关闭,则返回false
,来电者有责任对isClosed()
进行测试。
删除强>:
Throws exception,Special value:
表现得像空Queue
,来电者有责任测试isClosed()
。
关闭时会抛出NoSuchElementException
。
如果关闭,则返回null
,来电者有责任对isClosed()
进行测试。
<强>检查强>
没有变化。
我是通过修改source完成此操作,在github.com找到它。
答案 7 :(得分:0)
在这种情况下,您通常必须抛弃泛型并使队列保持类型为Object。那么,你只需要在转换为实际类型之前检查你的“毒药”对象。
答案 8 :(得分:0)
我使用过这个系统:
ConsumerClass
private boolean queueIsNotEmpty = true;//with setter
...
do {
...
sharedQueue.drainTo(docs);
...
} while (queueIsNotEmpty || sharedQueue.isEmpty());
当生产者完成时,我将consumerObject,queueIsNotEmpty字段设置为false
答案 9 :(得分:0)
今天我使用包装器对象解决了这个问题。由于ComplexObject对于子类太复杂,我将ComplexObject包装到ComplexObjectWrapper对象中。然后使用ComplexObjectWrapper作为泛型类型。
public class ComplexObjectWrapper {
ComplexObject obj;
}
public class EndOfQueue extends ComplexObjectWrapper{}
现在我做了BlockingQueue<ComplexObject>
而不是BlockingQueue<ComplexObjectWrapper>
{{1}}
由于我控制了消费者和生产者,这个解决方案对我有用。