take()でブロックしているBlockingQueueに割り込む方法は?


82

からオブジェクトを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?
}

回答:


72

スレッドを中断することができない場合は、MyObjHandlerによってそのように認識され、ループから抜け出す「マーカー」または「コマンド」オブジェクトをキューに配置することもできます。


39
これは「PoisonPillShutdown」アプローチとしても知られており、「Java ConcurrencyinPractice」の155-156ページで詳しく説明されています。
Brandon Yarbrough

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-将来の訪問者のために、ここでのアプローチはハックであり、3つの理由から本番コードで再現すべきではないことに注意してください。シャットダウンをフックする実際の方法を見つけることが常に推奨されます。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 2016年

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変数を作成しますvolatilestackoverflow.com/a/106787
lukk 2013

2
私が間違っていなければ、キューの最後の要素は処理されません。キューから最後の要素を取得すると、finishedはtrueであり、キューは空であるため、その最後の要素を処理する前に戻ります。3番目の条件を追加するif(Finished && queue.isEmpty()&& obj == null)
Matt R

@MattRありがとう、正しくアドバイスされた回答を編集します
dbw 2016年


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. ループwhile(!queue.isDone()||!queue.isEmpty())
  3. nullのtesttake()戻り値

1
以前の解決策はこれよりもクリーンでシンプルだったと
思い

1
完了したフラグは毒薬と同じですが、投与方法が異なります:)
David Mann

クリーナー?疑わしい。スレッドがいつ正確に中断されるかはわかりません。または私に尋ねさせてください、それで何がよりきれいですか?その少ないコード、それは事実です。
tomasb 2013

0

どうですか

queue.add(new MyObj())

一部のプロデューサースレッドでは、ストップフラグがコンシューマースレッドにwhileループを終了するように通知しますか?


1
受け入れられた回答(緑色の✓を探してください)や他の回答がある古い質問に回答する前に、あなたの回答が何か新しいものを追加するか、そうでなければそれらに関連して役立つことを確認してください。そのため、あなたの質問に対する答えは、OPの質問に対して受け入れられた答えによって与えられます。– StackOverflowのQ / A形式は、フォーラム形式のディスカッションを目的としたものではありません。「Xはどうですか?」と聞いて答えないでください。OPの質問には答えず、コメントです。コントリビューターガイドラインを参照してください。
Ivo Mori

...見たところ、私のメッセージは削除される可能性があります
Sam Ginrich
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.