Як перервати 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.