开发者

Why does the iterator.hasNext not work with BlockingQueue?

I was trying to use the iterator methods on a BlockingQueue and discovered that hasNext() is non-blocking - i.e. it will not wait until more elements are added and will instead return false when there are no elements.

So here are the questions :

  1. Is this bad design, or wrong expectation?
  2. Is there a way to use the blocking methods of the BLockingQueue with its parent Collection class methods (e.g. if some method were expecting a collection, can I pass a blocking queue and hope that its processing will wait until the Queue has more elements)

Here is a sample code block

public class SomeContainer{
     public static void main(String[] args){
        BlockingQueue bq = new L开发者_StackOverflow中文版inkedBlockingQueue();
        SomeContainer h = new SomeContainer();
        Producer p = new Producer(bq);
        Consumer c = new Consumer(bq);
        p.produce();
        c.consume();
    }

    static class Producer{
        BlockingQueue q;
        public Producer(BlockingQueue q) {
            this.q = q;
        }

        void produce(){
        new Thread(){
            public void run() {
            for(int i=0; i<10; i++){
                for(int j=0;j<10; j++){
                    q.add(i+" - "+j);
                }
                try {
                    Thread.sleep(30000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            };
        }.start();
        }
    }


    static class Consumer{
         BlockingQueue q;

         public Consumer(BlockingQueue q) {
             this.q = q;
         }

        void consume() {
            new Thread() {
                public void run() {
                    Iterator itr = q.iterator();
                    while (itr.hasNext())
                        System.out.println(itr.next());
                }
            }.start();
        }
        }
    }

This Code only prints the iteration once at the most.


Just don't use iterators with Queues. Use peek() or poll() instead or take() if it's a BlockingQueue:

void consume() {
    new Thread() {
        @Override
        public void run() {
            Object value;
            // actually, when using a BlockingQueue,
            // take() would be better than poll()
            while ((value=q.poll())!=null)
                System.out.println(value);
        }
    }.start();
}

A Queue is an Iterable because it is a Collection and hence needs to provide an iterator() method, but that shouldn't ever be used, or you shouldn't be using a Queue in the first place.


1) Is this bad design, or wrong expectation?

Wrong expectations since it would otherwise violate the contract of Iterator which on Iterator.next() says: Throws: NoSuchElementException - iteration has no more elements. If next() would block the exception would never be thrown.

2) Is there a way to use the blocking methods

Yes, for instance by extending the class and overriding the next and hasNext methods to use blocking routines instead. Note that hasNext would need to always return true in this case - which again violates the contract.


if an iterator blocked on hasNext then the iteration would never finish unless you explicitly broke out of it, this would be quite a strange design.

In any case the LinkedBlockingQueue javadoc has this to say

Returns an iterator over the elements in this queue in proper sequence. 
The returned <tt>Iterator</tt> is a "weakly consistent" iterator that will 
never throw {@link ConcurrentModificationException}, and guarantees to 
traverse elements as they existed upon construction of the iterator, and 
may (but is not guaranteed to) reflect any modifications subsequent to 
construction.


I think that it may be reasonable under certain circumstances to have an Iterable whose iterator() will block, although having a seperate BlockingIteratorwould be foolish. The reason for this is because that lests you use an enhanced for loop, which can,in some cases, make your code cleaner. (If it would not accomplish that in your particular circumstance, do not do this at all.)

for(Request request:requests) process(request);

However, the iterator is still not free from a termination condition! The iterator should terminate once the queue has been closed to new items, and runs out of elements.

The issue still remains, though, that if the loop was already blocking on the iterator's next() method, the only way to exit if the queue is closed is to throw an exception, which the surrounding code would need to handle correctly, If you choose to do this, make sure you explain very clearly and precisely, how your implementation works in the javadoc comments.


The Iterator for LinkedBlockingQueue has this as its hasNext implementation:

  private Node<E> current;

   public boolean hasNext() {
        return current != null;
    }

so this will only work per call. You can wrap the method in a while(true) loop if you want to wait for elements and use the standard java Iterator idiom:

    while (true) {     
       if(itr.hasNext()) {
          System.out.println(itr.next());
        }
    }
0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜