Java LinkedBlockingQueue能否在完成时发出信号?

时间:2012-03-03 07:10:59

标签: java producer-consumer

我有一个生产者和单个消费者使用对象队列的情况。队列可能为空时有两种情况:

  1. 消费者处理对象的速度比生产者能够生成新对象的速度快(生产者在生成对象之前使用I / O)。
  2. 制作人已完成生成对象。
  3. 如果队列为空,我希望消费者等到新对象可用或者直到生产者发出信号表明它已完成为止。

    到目前为止,我的研究没有让我无处可去,因为我最终得到了一个循环来检查队列和一个单独的布尔标志(isDone)。鉴于没有办法等待多个锁(想到等待队列和标志),可以做些什么来解决这个问题?

5 个答案:

答案 0 :(得分:2)

首先,使用包装器“太多开销”的建议是一种猜测,IMO非常糟糕。该假设应测量,并进行具有实际要求的性能测试。当且仅当测试失败时,请使用包装队列对象的探查器进行验证。

仍然如果你这样做并且包装队列对象(在这种情况下是一个String)确实是不可接受的性能的原因,那么你可以使用这种技术:创建一个已知的唯一字符串作为“消息结束”消息。

    public static final String NO_MORE_MESSAGES = UUID.randomUUID().toString();

然后,当从队列中检索字符串时,如果字符串是NO_MORE_MESSAGES,只需检查(它可以是引用检查)。如果是这样,那么你就完成了处理。

答案 1 :(得分:1)

简单。定义一个特殊的对象,生产者可以发送信号“完成”。

答案 2 :(得分:1)

一种选择是将数据包装在持有者对象中,该对象可用于表示处理结束。

例如:

public class QueueMessage {
 public MessageType type;
 public Object thingToWorkOn;
}

其中MessageType是定义“工作”消息或“关闭”消息的枚举。

答案 3 :(得分:1)

您可以在使用者中使用LinkedBlockingQueues poll(long timeout, TimeUnit unit) -method,如果它返回null(已经过了时间),请检查布尔标志。另一种方法是将一些特殊的“EndOfWork”对象作为最后一个传递到队列中,因此消费者知道这是工作的结束。

另一种方法是从生产者线程中断消费者线程,但这需要生产者线程知道消费者。如果它们都将被实现为嵌套类,则可以使用父类来保存布尔运行值,它们都可以访问,并使用单个布尔值终止两个线程。

答案 4 :(得分:1)

以下选项也已被提出(不确定这是否应该是对我自己的答案,但找不到更好的地方来写这个):

为队列创建一个包装器。这个包装器将有一个监视器,当消费者读取时它将被等待,并且无论何时添加新对象或者提出isDone标志,生成器都会通知它。

当消费者从队列中读取对象时,这些对象将被包装类似于上面提到的@ yann-ramin。但是为了减少开销,消费者将在每次读取调用时提供单个可重用的QueueMessage实例(它将始终是同一个实例)。在将实例返回给使用者之前,队列包装器将相应地更新字段。

这可以避免使用超时,睡眠等。

<强> EDITED 这是一个建议的实施:

/**
 * This work queue is designed to be used by ONE producer and ONE consumer
 * (no more, no less of neither). The work queue has certain added features, such
 * as the ability to signal that the workload generation is done and nothing will be
 * added to the queue.
 *
 * @param <E>
 */
public class DefiniteWorkQueue<E> {
    private final E[] EMPTY_E_ARRAY;
    private LinkedBlockingQueue<E> underlyingQueue = new LinkedBlockingQueue<E>();
    private boolean isDone = false;

    // This monitor allows for flagging when a change was done.
    private Object changeMonitor = new Object();

    public DefiniteWorkQueue(Class<E> clazz) {
        // Reuse this instance, makes calling toArray easier
        EMPTY_E_ARRAY = (E[]) Array.newInstance(clazz, 0);
    }

    public boolean isDone() {
        return isDone;
    }

    public void setIsDone() {
        synchronized (changeMonitor) {
            isDone = true;
            changeMonitor.notifyAll();
        }
    }

    public int size() {
        return underlyingQueue.size();
    }

    public boolean isEmpty() {
        return underlyingQueue.isEmpty();
    }

    public boolean contains(E o) {
        return underlyingQueue.contains(o);
    }

    public Iterator<E> iterator() {
        return underlyingQueue.iterator();
    }

    public E[] toArray() {
        // The array we create is too small on purpose, the underlying
        // queue will extend it as needed under a lock
        return underlyingQueue.toArray(EMPTY_E_ARRAY);
    }

    public boolean add(E o) {
        boolean retval;
        synchronized (changeMonitor) {
            retval = underlyingQueue.add(o);
            if (retval)
                changeMonitor.notifyAll();
        }
        return retval;
    }

    public boolean addAll(Collection<? extends E> c) {
        boolean retval;
        synchronized (changeMonitor) {
            retval = underlyingQueue.addAll(c);
            if (retval)
                changeMonitor.notifyAll();
        }
        return retval;
    }

    public void remove(RemovalResponse<E> responseWrapper) throws InterruptedException {
        synchronized (changeMonitor) {
            // If there's nothing in the queue but it has not
            // ended yet, wait for someone to add something.
            if (isEmpty() && !isDone())
                changeMonitor.wait();

            // When we get here, we've been notified or
            // the current underlying queue's state is already something
            // we can respond about.
            if (!isEmpty()) {
                responseWrapper.type = ResponseType.ITEM;
                responseWrapper.item = underlyingQueue.remove();
            } else if (isDone()) {
                responseWrapper.type = ResponseType.IS_DONE;
                responseWrapper.item = null;
            } else {
                // This should not happen
                throw new IllegalStateException(
                    "Unexpected state where a notification of change was     made but " +
                        "nothing is in the queue and work is not     done.");
            }
        }
    }

    public static class RemovalResponse<E> {
        public enum ResponseType {
            /**
             * Used when the response contains the first item of the queue.
             */
            ITEM,

            /**
             * Used when the work load is done and nothing new will arrive.
             */
            IS_DONE
        };

        private ResponseType type;
        private E item;

        public ResponseType getType() {
            return type;
        }

        public void setType(ResponseType type) {
            this.type = type;
        }

        public E getItem() {
            return item;
        }

        public void setItem(E item) {
            this.item = item;
        }

    }
}