如何中断在take()上阻塞的BlockingQueue?


82

我有一个类,该类从a获取对象BlockingQueue并通过take()连续循环调用来处理它们。在某些时候,我知道不会再有其他对象添加到队列中。如何中断该take()方法以使其停止阻塞?

这是处理对象的类:

public class MyObjHandler implements Runnable {

  private final BlockingQueue<MyObj> queue;

  public class MyObjHandler(BlockingQueue queue) {
    this.queue = queue;
  }

  public void run() {
    try {
      while (true) {
        MyObj obj = queue.take();
        // process obj here
        // ...
      }
    } catch (InterruptedException e) {
      Thread.currentThread().interrupt();
    }
  }
}

这是使用此类处理对象的方法:

public void testHandler() {

  BlockingQueue<MyObj> queue = new ArrayBlockingQueue<MyObj>(100);  

  MyObjectHandler  handler = new MyObjectHandler(queue);
  new Thread(handler).start();

  // get objects for handler to process
  for (Iterator<MyObj> i = getMyObjIterator(); i.hasNext(); ) {
    queue.put(i.next());
  }

  // what code should go here to tell the handler
  // to stop waiting for more objects?
}

Answers:


72

如果不能选择中断线程,则另一种方法是将“标记”或“命令”对象放在MyObjHandler可以识别的队列上,并退出循环。


39
这也称为“中毒药丸关闭”方法,在“ Java并发实践”中,特别是在155-156页中进行了详细讨论。
布兰登·雅伯

14
BlockingQueue<MyObj> queue = new ArrayBlockingQueue<MyObj>(100);
MyObjectHandler handler = new MyObjectHandler(queue);
Thread thread = new Thread(handler);
thread.start();
for (Iterator<MyObj> i = getMyObjIterator(); i.hasNext(); ) {
  queue.put(i.next());
}
thread.interrupt();

但是,如果执行此操作,则在队列中仍有其他项目等待处理时,线程可能会中断。您可能需要考虑使用poll而不是take,这将使处理线程在等待一段时间且没有新输入的情况下超时并终止。


是的,如果在队列中仍有项目时线程被中断,那就是一个问题。为解决此问题,我添加了代码以确保在中断线程之前队列为空:<code> while(queue.size()> 0)Thread.currentThread()。sleep(5000); </ code>
MCS

3
@MCS-为将来的访问者注意,您的方法是一种hack,不应出于以下三个原因在生产代码中进行复制。总是首选找到挂机的实际方法。不能使用它来Thread.sleep()代替适当的钩子。在其他实现中,其他线程可能会将事物放入队列中,而while循环可能永远不会结束。
埃里克·罗伯逊

值得庆幸的是,无需依赖这种黑客手段,因为如果需要的话在中断也可以轻松地对其进行处理。例如,一个“详尽的”take()实现可能看起来像:try { return take(); } catch (InterruptedException e) { E o = poll(); if (o == null) throw e; Thread.currentThread().interrupt(); return o; } 但是,没有理由在这一层上实现它,并且稍微更高一点地实现它将导致更有效的代码(例如,通过避免按元素InterruptedException和/或通过使用BlockingQueue.drainTo())。
antak

13

很晚了,但是希望当我遇到类似的问题并使用上面埃里克森poll建议的方法进行一些细微更改后,这对其他人也有帮助

class MyObjHandler implements Runnable 
{
    private final BlockingQueue<MyObj> queue;
    public volatile boolean Finished;  //VOLATILE GUARANTEES UPDATED VALUE VISIBLE TO ALL
    public MyObjHandler(BlockingQueue queue) 
    {
        this.queue = queue;
        Finished = false;
    }
    @Override
    public void run() 
    {        
        while (true) 
        {
            try 
            {
                MyObj obj = queue.poll(100, TimeUnit.MILLISECONDS);
                if(obj!= null)//Checking if job is to be processed then processing it first and then checking for return
                {
                    // process obj here
                    // ...
                }
                if(Finished && queue.isEmpty())
                    return;

            } 
            catch (InterruptedException e) 
            {                   
                return;
            }
        }
    }
}

public void testHandler() 
{
    BlockingQueue<MyObj> queue = new ArrayBlockingQueue<MyObj>(100); 

    MyObjHandler  handler = new MyObjHandler(queue);
    new Thread(handler).start();

    // get objects for handler to process
    for (Iterator<MyObj> i = getMyObjIterator(); i.hasNext(); )
    {
        queue.put(i.next());
    }

    // what code should go here to tell the handler to stop waiting for more objects?
    handler.Finished = true; //THIS TELLS HIM
    //If you need you can wait for the termination otherwise remove join
    myThread.join();
}

这解决了两个问题

  1. 标记了 BlockingQueue,以使其不必等待更多元素
  2. 两次之间没有中断,以便仅在处理队列中的所有项目并且没有剩余要添加的项目时处理块才终止

2
设置Finished变量volatile以确保线程之间的可见性。参见stackoverflow.com/a/106787
2013年

2
如果我没记错的话,队列中的最后一个元素将不会被处理。当您从队列中获取最后一个元素时,finished为true,并且队列为空,因此它将在处理该最后一个元素之前返回。如果(Finished && queue.isEmpty()&& obj == null)
Matt R

@MattR谢谢,正确建议我将编辑发布的答案
dbw


0

或不要打扰,它令人讨厌。

    public class MyQueue<T> extends ArrayBlockingQueue<T> {

        private static final long serialVersionUID = 1L;
        private boolean done = false;

        public ParserQueue(int capacity) {  super(capacity); }

        public void done() { done = true; }

        public boolean isDone() { return done; }

        /**
         * May return null if producer ends the production after consumer 
         * has entered the element-await state.
         */
        public T take() throws InterruptedException {
            T el;
            while ((el = super.poll()) == null && !done) {
                synchronized (this) {
                    wait();
                }
            }

            return el;
        }
    }
  1. 当生产者将对象放入队列时,调用 queue.notify(),如果结束,则调用queue.done()
  2. 同时循环(!queue.isDone()||!queue.isEmpty())
  3. 测试take()返回null的值

1
我要说的是,以前的解决方案比这个解决方案更干净,更简单
sakthisundar 2012年

1
完成标志与毒药相同,只是给药方式不同:)
David Mann 2013年

清洁器?我对此表示怀疑。您不知道线程何时确切中断。还是让我问,什么更清洁?它的代码更少,这是事实。
tomasb

0

那一个呢

queue.add(new MyObj())

在某些生产者线程中,哪里有一个停止标志指示使用者线程结束while循环?


1
在回答具有可接受的答案(寻找绿色✓)以及其他答案的旧问题之前,请确保您的答案添加了新的内容或对他们有所帮助。这样,您的问题的答案就由OP问题的公认答案给出。– Stack Overflow的Q / A格式不适用于论坛式的讨论。不要通过询问“如何做X?”来给出答案。因为它没有回答OP的问题,而是一条评论。请参阅贡献者指南
Ivo Mori

...看到了,我的信息可能会被删除
Sam Ginrich
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.