Як перервати BlockingQueue, який блокує на take ()?


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?
}

Відповіді:


72

Якщо переривання потоку не є можливим, іншим є розміщення об’єкта «маркер» або «команда» в черзі, який буде розпізнаний як такий MyObjHandler і вирветься з циклу.


40
Це також відомо як підхід "вимкнення отруйних таблеток", який детально обговорюється в "Паралельності 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 - зверніть увагу на майбутніх відвідувачів, що ваш підхід тут - хакерство, і його не слід відтворювати у виробничому коді з трьох причин. Завжди бажано знайти реальний спосіб зачепити відключення. Ніколи не прийнятно використовувати 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()).
антак

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
lukk

2
Якщо я не помиляюсь, остаточний елемент у черзі не буде оброблений. Коли ви берете кінцевий елемент з черги, готовий 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. цикл while (! queue.isDone () ||! queue.isEmpty ())
  3. test take () повертає значення для null

1
Я б сказав, що попереднє рішення було чистішим і простішим за це
sakthisundar

1
Виконаний прапор такий самий, як таблетка для отрути, його просто вводять по-різному :)
Девід Манн,

Прибиральник? Я сумніваюся. Ви не знаєте, коли потік точно переривається. Або дозвольте запитати, що з цим чистіше? Це менше коду, це факт.
tomasb

0

А як щодо a

queue.add(new MyObj())

в деякому потоці виробника, де прапор зупинки сигналізує про те, що споживчий потік закінчується циклом while?


1
Перш ніж відповісти на старе запитання, отримавши прийняту відповідь (шукайте зелений ✓), а також інші відповіді, переконайтеся, що ваша відповідь додає щось нове або є іншим корисним стосовно них. Як така відповідь на ваше запитання дає прийнята відповідь на запитання ОП. - Формат Q / A Stack Overflow не призначений для обговорень у стилі форуму. Не дайте відповіді, запитуючи "як щодо X?" оскільки це не відповідає на питання ОП, а скоріше є коментарем. Будь ласка, зверніться до Рекомендацій для співавторів .
Іво Морі

... бачимо, моє повідомлення може бути видалено
Сем Гінріч
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.