Take () üzerinde bloke eden bir BlockingQueue nasıl kesilir?


82

Nesneleri a'dan alan BlockingQueueve onları take()sürekli bir döngüde çağırarak işleyen bir sınıfım var . Bir noktada, kuyruğa başka nesnelerin eklenmeyeceğini biliyorum. take()Engellemeyi durdurması için yöntemi nasıl keserim?

İşte nesneleri işleyen sınıf:

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();
    }
  }
}

Ve işte bu sınıfı nesneleri işlemek için kullanan yöntem:

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

Yanıtlar:


72

İş parçacığını kesmek bir seçenek değilse, bir diğeri de MyObjHandler tarafından bu şekilde tanınacak ve döngüden çıkacak olan kuyruğa bir "işaret" veya "komut" nesnesi yerleştirmektir.


40
Bu aynı zamanda 'Zehirli Hap Kapatma' yaklaşımı olarak da bilinir ve özellikle 155-156. Sayfalarda, "Uygulamada Java Eş Zamanlılığı" nda ayrıntılı olarak tartışılmıştır.
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();

Ancak, bunu yaparsanız, kuyrukta hala işlenmeyi bekleyen öğeler varken iş parçacığı kesintiye uğrayabilir. Bunun pollyerine kullanmayı düşünebilirsiniz take, bu işlem iş parçacığının zaman aşımına uğramasına ve bir süre yeni girdi olmadan beklediğinde sona ermesine izin verecektir.


Evet, kuyrukta hala öğeler varken iş parçacığı kesilirse bu bir sorundur. Bunu aşmak için, iş parçacığını kesmeden önce sıranın boş olduğundan emin olmak için kod ekledim: <code> while (queue.size ()> 0) Thread.currentThread (). Sleep (5000); </code>
MCS

3
@MCS - gelecekteki ziyaretçiler için buradaki yaklaşımınızın bir hack olduğunu ve üç nedenden dolayı üretim kodunda yeniden üretilmemesi gerektiğini unutmayın. Her zaman kapatmanın gerçek bir yolunu bulmak tercih edilir. Thread.sleep()Uygun bir kancaya alternatif olarak kullanılması asla kabul edilemez . Diğer uygulamalarda, diğer evreler işleri kuyruğa yerleştiriyor olabilir ve while döngüsü hiç bitmeyebilir.
Erick Robertson

Neyse ki, bu tür saldırılara güvenmeye gerek yok çünkü biri gerektiğinde kesmeden sonra onu kolayca işliyor . Örneğin, "kapsamlı" bir take()uygulama şöyle görünebilir: try { return take(); } catch (InterruptedException e) { E o = poll(); if (o == null) throw e; Thread.currentThread().interrupt(); return o; } Bununla birlikte, bu katmanda uygulanması için bir neden yoktur ve biraz daha yükseğe uygulamak, daha verimli kodlara yol açar (örneğin, öğe başına InterruptedExceptionve / veya kullanarak BlockingQueue.drainTo()).
antak

13

Çok geç ama umarım bu , benzer problemle karşılaştığım ve yukarıdaki ericksonpoll tarafından önerilen yaklaşımı bazı küçük değişikliklerle kullandığım için başkalarına da yardımcı olur

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();
}

Bu her iki sorunu da çözdü

  1. BlockingQueueÖğeleri daha fazla beklememesi gerektiğini bilmesi için işaretlendi
  2. İşlem blokları yalnızca kuyruktaki tüm öğeler işlendiğinde ve eklenecek öğe kalmadığında sona ereceği için arada kesintiye uğramadı

2
Dişler arasında görünürlüğü garanti etmek için Finisheddeğişken yapın volatile. Bkz stackoverflow.com/a/106787
Lukk

2
Yanılmıyorsam, kuyruktaki son öğe işlenmeyecek. Kuyruktan son öğeyi aldığınızda, tamamlanmış doğru olur ve kuyruk boştur, bu nedenle son öğeyi işlemeden önce geri dönecektir. Üçüncü bir koşul ekleyin (Finished && queue.isEmpty () && obj == null)
Matt R

@MattR teşekkürler, doğru tavsiye edilen cevabı düzenleyeceğim
dbw


0

Ya da sözünü kesme, iğrenç.

    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. yapımcı kuyruğa nesne koyduğunda, ara queue.notify(), biterse araqueue.done()
  2. döngü sırasında (! queue.isDone () ||! queue.isEmpty ())
  3. null için test take () dönüş değeri

1
Önceki çözümün daha temiz ve bundan daha basit olduğunu
söyleyebilirim

1
Yapılan bayrak, bir zehir hapı ile aynıdır, sadece farklı şekilde uygulanır :)
David Mann

Temizleyici mi? Şüpheliyim. İpliğin tam olarak ne zaman kesildiğini bilmiyorsunuz. Ya da sormama izin verin, bunda daha temiz olan ne? Daha az kod, bu bir gerçek.
2013

0

Ne hakkında

queue.add(new MyObj())

Bazı üretici iş parçacığında, bir durdurma işaretinin tüketici iş parçacığının while döngüsünü bitirmesini işaret ettiği durumlarda?


1
Kabul edilen bir cevaba sahip eski bir soruyu cevaplamadan önce (yeşili arayın ✓) ve diğer cevapların yanı sıra cevabınızın yeni bir şey eklediğinden veya bunlarla ilgili başka bir şekilde yardımcı olduğundan emin olun. Bu nedenle, sorunuzun cevabı , OP'nin sorusu için kabul edilen cevapla verilmektedir . - Stack Overflow'un Soru / Cevap formatı forum tarzı tartışmalar için tasarlanmamıştır. "X yapmaya ne dersin?" Diye sorarak cevap vermeyin. çünkü OP'nin sorusuna cevap vermekten ziyade bir yorumdur. Lütfen Katılımcı Yönergelerine bakın .
Ivo Mori

... görüldü, mesajım kaldırılabilir
Sam Ginrich
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.