Làm cách nào để ngắt một BlockingQueue đang chặn trên take ()?


82

Tôi có một lớp lấy các đối tượng từ a BlockingQueuevà xử lý chúng bằng cách gọi take()trong một vòng lặp liên tục. Tại một thời điểm nào đó, tôi biết rằng sẽ không có thêm đối tượng nào được thêm vào hàng đợi. Làm cách nào để ngắt take()phương thức để nó ngừng chặn?

Đây là lớp xử lý các đối tượng:

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

Và đây là phương thức sử dụng lớp này để xử lý các đối tượng:

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

Câu trả lời:


72

Nếu ngắt luồng không phải là một tùy chọn, thì một tùy chọn khác là đặt một đối tượng "điểm đánh dấu" hoặc "lệnh" trên hàng đợi sẽ được MyObjHandler nhận dạng như vậy và thoát ra khỏi vòng lặp.


39
Đây còn được gọi là cách tiếp cận 'Ngắt thuốc độc' và được thảo luận nhiều trong "Java Concurrency in Practice", cụ thể là trên trang 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();

Tuy nhiên, nếu bạn làm điều này, chuỗi có thể bị gián đoạn trong khi vẫn còn các mục trong hàng đợi để xử lý. Bạn có thể muốn xem xét sử dụng pollthay vì take, điều này sẽ cho phép luồng xử lý hết thời gian chờ và kết thúc khi nó đã đợi một lúc mà không có đầu vào mới.


Có, đó là một vấn đề nếu chuỗi bị gián đoạn trong khi vẫn còn các mục trong hàng đợi. Để giải quyết vấn đề này, tôi đã thêm mã để đảm bảo hàng đợi trống trước khi làm gián đoạn chuỗi: <code> while (queue.size ()> 0) Thread.currentThread (). Sleep (5000); </code>
MCS

3
@MCS - lưu ý cho những khách truy cập trong tương lai rằng cách tiếp cận của bạn ở đây là một cuộc tấn công và không nên được sao chép lại trong mã sản xuất vì ba lý do. Nó luôn được ưu tiên tìm một cách thực tế để kết nối việc tắt máy. Nó không bao giờ được chấp nhận để sử dụng Thread.sleep()như một sự thay thế cho một cái móc thích hợp. Trong các triển khai khác, các luồng khác có thể đang đặt mọi thứ vào hàng đợi và vòng lặp while có thể không bao giờ kết thúc.
Erick Robertson

Rất may, không cần phải dựa vào các bản hack như vậy vì người ta có thể dễ dàng xử lý nó sau khi ngắt nếu cần thiết. Ví dụ: take()triển khai "toàn diện" có thể trông như sau: try { return take(); } catch (InterruptedException e) { E o = poll(); if (o == null) throw e; Thread.currentThread().interrupt(); return o; } Tuy nhiên, không có lý do gì nó cần được triển khai ở lớp này và việc triển khai nó cao hơn một chút sẽ dẫn đến mã hiệu quả hơn (chẳng hạn như bằng cách tránh mỗi phần tử InterruptedExceptionvà / hoặc bằng cách sử dụng BlockingQueue.drainTo()).
antak

13

Rất muộn nhưng Hy vọng điều này cũng sẽ giúp ích cho những người khác khi tôi gặp phải vấn đề tương tự và sử dụng pollcách tiếp cận được đề xuất bởi erickson ở trên với một số thay đổi nhỏ,

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

Điều này giải quyết được cả hai vấn đề

  1. Gắn cờ BlockingQueueđể biết rằng nó không phải đợi thêm các phần tử
  2. Không bị gián đoạn ở giữa để các khối xử lý chỉ kết thúc khi tất cả các mục trong hàng đợi được xử lý và không còn mục nào để thêm

2
Tạo Finishedbiến volatileđể đảm bảo khả năng hiển thị giữa các luồng. Xem stackoverflow.com/a/106787
lukk

2
Nếu tôi không nhầm, phần tử cuối cùng trong hàng đợi sẽ không được xử lý. Khi bạn lấy phần tử cuối cùng từ hàng đợi, kết thúc là true và hàng đợi trống, vì vậy nó sẽ trả về trước khi xử lý phần tử cuối cùng đó. Thêm điều kiện thứ ba nếu (Đã kết thúc && queue.isEmpty () && obj == null)
Matt R

@MattR cảm ơn, đã tư vấn chính xác, tôi sẽ chỉnh sửa câu trả lời đã đăng
dbw


0

Hoặc đừng làm gián đoạn, nó khó chịu.

    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. khi nhà sản xuất đặt đối tượng vào hàng đợi, hãy gọi queue.notify(), nếu nó kết thúc, hãy gọiqueue.done()
  2. vòng lặp while (! queue.isDone () ||! queue.isEmpty ())
  3. test take () trả về giá trị cho null

1
Tôi có thể nói rằng giải pháp trước đây là một trình dọn dẹp và đơn giản hơn này
sakthisundar

1
Lá cờ done là giống như một viên thuốc độc, nó chỉ quản lý khác nhau :)
David Mann

Sạch hơn? Tôi nghi ngờ điều đó. Bạn không biết chính xác khi nào thì luồng bị gián đoạn. Hay cho mình hỏi, cái gì sạch hơn với cái đó? Nó ít mã hơn, đó là một sự thật.
tomasb

0

Còn về một

queue.add(new MyObj())

trong một số chuỗi nhà sản xuất, nơi cờ dừng báo hiệu chuỗi người tiêu dùng kết thúc vòng lặp trong khi?


1
Trước khi trả lời một câu hỏi cũ, có một câu trả lời được chấp nhận (tìm màu xanh lá cây ✓) cũng như các câu trả lời khác, hãy đảm bảo câu trả lời của bạn thêm điều gì đó mới hoặc hữu ích liên quan đến chúng. Như vậy, câu trả lời cho câu hỏi của bạn được đưa ra bởi câu trả lời được chấp nhận cho câu hỏi của OP. - Định dạng Q / A của Stack Overflow không dành cho các cuộc thảo luận kiểu diễn đàn. Đừng đưa ra câu trả lời bằng cách hỏi "làm thế nào về việc X?" vì nó không trả lời câu hỏi của OP mà là một nhận xét. Vui lòng tham khảo Nguyên tắc dành cho cộng tác viên .
Ivo Mori

... đã thấy, tin nhắn của tôi có thể bị xóa
Sam Ginrich
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.