“Closing” a blocking queue

前端 未结 10 678
不思量自难忘°
不思量自难忘° 2021-01-30 09:13

I’m using java.util.concurrent.BlockingQueue in a very simple producer-consumer scenario. E.g. this pseudo code depicts the consumer part:

class QueueCo         


        
相关标签:
10条回答
  • 2021-01-30 09:41

    Another possibility for making a poison object: Make it be a particular instance of the class. This way, you do not have to muck around subtypes or screw up your generic.

    Drawback: This won't work if there's some sort of serialization barrier between the producer and consumer.

    public class ComplexObject
    {
        public static final POISON_INSTANCE = new ComplexObject();
    
        public ComplexObject(whatever arguments) {
        }
    
        // Empty constructor for creating poison instance.
        private ComplexObject() {
        }
    }
    
    class QueueConsumer implements Runnable {
        @Override
        public void run() {
            while(!(Thread.currentThread().interrupted())) {
                try {
                    final ComplexObject complexObject = myBlockingQueue.take();
                    if (complexObject == ComplexObject.POISON_INSTANCE)
                        return;
    
                    // Process complex object.
    
                } catch (InterruptedException e) {
                    // Set interrupted flag.
                    Thread.currentThread().interrupt();
                }
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-30 09:42

    It seems reasonable to me to implement a close-able BlockingQueue:

    import java.util.concurrent.BlockingQueue;
    
    public interface CloseableBlockingQueue<E> extends BlockingQueue<E> {
        /** Returns <tt>true</tt> if this queue is closed, <tt>false</tt> otherwise. */
        public boolean isClosed();
    
        /** Closes this queue; elements cannot be added to a closed queue. **/
        public void close();
    }
    

    It would be quite straight forward to implement this with the following behaviours (cf. the methods summary table):

    • Insert:

      • Throws exception, Special value:

        Behaves like a full Queue, caller's responsibility to test isClosed().

      • Blocks:

        Throws IllegalStateException if and when closed.

      • Times out:

        Returns false if and when closed, caller's responsibility to test isClosed().

    • Remove:

      • Throws exception, Special value:

        Behaves like a empty Queue, caller's responsibility to test isClosed().

      • Blocks:

        Throws NoSuchElementException if and when closed.

      • Times out:

        Returns null if and when closed, caller's responsibility to test isClosed().

    • Examine

      No change.

    I did this by editing the source, find it at github.com.

    0 讨论(0)
  • 2021-01-30 09:47

    In this situation, you generally have to ditch the generics and make the queue hold type Object. then, you just need check for your "poison" Object before casting to the actual type.

    0 讨论(0)
  • 2021-01-30 09:49

    Another idea for making this simple:

    class ComplexObject implements QueueableComplexObject
    {
        /* the meat of your complex object is here as before, just need to
         * add the following line and the "implements" clause above
         */
        @Override public ComplexObject asComplexObject() { return this; }
    }
    
    enum NullComplexObject implements QueueableComplexObject
    {
        INSTANCE;
    
        @Override public ComplexObject asComplexObject() { return null; }
    }
    
    interface QueueableComplexObject
    {
        public ComplexObject asComplexObject();
    }
    

    Then use BlockingQueue<QueueableComplexObject> as the queue. When you wish to end the queue's processing, do queue.offer(NullComplexObject.INSTANCE). On the consumer side, do

    boolean ok = true;
    while (ok)
    {
        ComplexObject obj = queue.take().asComplexObject();
        if (obj == null)
            ok = false;
        else
            process(obj);
    }
    
    /* interrupt handling elided: implement this as you see fit,
     * depending on whether you watch to swallow interrupts or propagate them
     * as in your original post
     */
    

    No instanceof required, and you don't have to construct a fake ComplexObject which may be expensive/difficult depending on its implementation.

    0 讨论(0)
  • 2021-01-30 09:51

    If you have a handle to the consumer thread, you can interrupt it. With the code you gave, that will kill the consumer. I would not expect the producer to have this; it would probably have to callback to the program controller somehow to let it know it's done. Then the controller would interrupt the consumer thread.

    You can always finish doing work before obeying the interrupt. For instance:

    class QueueConsumer implements Runnable {
        @Override
        public void run() {
            while(!(Thread.currentThread().isInterrupted())) {
                try {
                    final ComplexObject complexObject = myBlockingQueue.take();
                    this.process(complexObject);
    
                } catch (InterruptedException e) {
                    // Set interrupted flag.
                    Thread.currentThread().interrupt();
                }
            }
    
            // Thread is getting ready to die, but first,
            // drain remaining elements on the queue and process them.
            final LinkedList<ComplexObject> remainingObjects;
            myBlockingQueue.drainTo(remainingObjects);
            for(ComplexObject complexObject : remainingObjects) {
                this.process(complexObject);
            }
        }
    
        private void process(final ComplexObject complexObject) {
            // Do something with the complex object.
        }
    }
    

    I would actually prefer that to somehow poisoning the queue anyway. If you want to kill the thread, ask the thread to kill itself.

    (It's nice to see someone handling InterruptedException properly.)


    There seems to be some contention about the handling of interruptions here. First, I would like everyone to read this article: http://www.ibm.com/developerworks/java/library/j-jtp05236.html

    Now, with the understanding that no one actually read that, here's the deal. A thread will only receive an InterruptedException if it was currently blocking at the time of interrupt. In this case, Thread.interrupted() will return false. If it was not blocking, it will NOT receive this exception, and instead Thread.interrupted() will return true. Therefore, your loop guard should absolutely, no matter what, check Thread.interrupted(), or otherwise risk missing an interruption to the thread.

    So, since you are checking Thread.interrupted() no matter what, and you are forced to catch InterruptedException (and should be dealing with it even if you weren't forced to), you now have two code areas which handle the same event, thread interruption. One way to handle this is normalize them into one condition, meaning either the boolean state check can throw the exception, or the exception can set the boolean state. I choose the later.


    Edit: Note that the static Thread#interrupted method clears the the interrupted status of the current thread.

    0 讨论(0)
  • 2021-01-30 09:53

    I have used this system:

    ConsumerClass
    private boolean queueIsNotEmpty = true;//with setter
    ...
    do {
        ...
        sharedQueue.drainTo(docs);
        ...
    } while (queueIsNotEmpty || sharedQueue.isEmpty());
    

    When producer finish, I set on consumerObject, queueIsNotEmpty field to false

    0 讨论(0)
提交回复
热议问题