如何实现PriorityBlockingQueue的循环顺序?

时间:2015-01-02 05:33:13

标签: java multithreading priority-queue java.util.concurrent round-robin

我有PriorityBlockingQueue。单个线程一次从该队列中消耗一条消息并对其进行处理。另外几个线程正在将消息插入队列。生产者线程为他们提交的每条消息分配一个完整的优先级。静态AtomicLong用于为每个消息分配唯一的,单调递增的ID。队列的Comparator首先按此优先级排序消息,然后按ID排序相同的优先级消息(最低ID排在第一位。)

问题:有时一个制作人会提交大量的邮件。然后,这使得其他制作者处理他们的消息。我想做的是让生产者之间的消费者循环使用相同优先级的消息(同时仍然按提交顺序处理单个生产者的相同优先级消息)。但我无法弄清楚如何编写Comparator来执行此操作。

我考虑的另一个选择是为每个制作人提供一个单独的队列。但是,我不认为这可行,因为我不知道单线程在多个队列上等待的任何方法。

4 个答案:

答案 0 :(得分:3)

对于每个制作人来说,我觉得用一个Queue来实现这个更直接。一个线程不能在多个Queue上等待,但您可以将所有Queue组合成一个帮助程序类,以便它不需要。

import java.util.ArrayList;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import javax.annotation.concurrent.GuardedBy;

public class RoundRobin<P, E> {
    private final Lock lock = new ReentrantLock();
    private final Condition added = lock.newCondition();

    @GuardedBy("lock") private final Map<P, Queue<E>> queues = new LinkedHashMap<>();

    public boolean add(P producer, E item) {
        lock.lock();
        try {
            if (!queues.containsKey(producer)) {
                queues.put(producer, new PriorityBlockingQueue<>());
            }

            added.signalAll();
            return queues.get(producer).add(item);
        } finally {
            lock.unlock();
        }
    }

    public Iterator<E> roundRobinIterator() {
        return new Iterator<E>() {
            private Iterator<? extends Queue<E>> i = null;
            private boolean singlePass = true;

            @Override
            public boolean hasNext() {
                return true;
            }

            @Override
            public E next() {
                lock.lock();
                try {
                    while (true) {
                        if (i == null || !i.hasNext()) {
                            i = queues.values().iterator();
                            singlePass = true;
                        }

                        while (i.hasNext()) {
                            Queue<E> q = i.next();
                            if (!q.isEmpty()) {
                                if (singlePass) {
                                    // copy the iterator to prevent
                                    // ConcurrentModificationExceptions
                                    singlePass = false;
                                    i = copy(i);
                                }
                                return q.poll();
                            }
                        }

                        if (singlePass) {
                            // If singlePass is true then we just checked every
                            // queue and they were all empty.
                            // Wait for another element to be added.
                            added.await();
                        }
                    }
                } catch (InterruptedException e) {
                    throw new NoSuchElementException(e.getMessage());
                } finally {
                    lock.unlock();
                }
            }

            private <T> Iterator<? extends T> copy(Iterator<? extends T> i) {
                List<T> copy = new ArrayList<>();
                while (i.hasNext()) {
                    copy.add(i.next());
                }
                return copy.iterator();
            }
        };
    }
}

答案 1 :(得分:2)

我想我会做那样的事情:

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;

public class RRQueue<M> {
    private final ThreadLocal<Queue<M>> threadQueue = new ThreadLocal<>();
    private final List<Queue<M>> queues;
    private int current = 0;

    public RRQueue() {
        this.queues = new ArrayList<>();
    }

    public synchronized void add(M msg) {
        Queue<M> queue = threadQueue.get();
        if (queue == null) {
            queue = new LinkedList<>(); // or whatever
            queues.add(queue);
            threadQueue.set(queue);
        }
        queue.add(msg);
        notify();
    }

    public synchronized M get() throws InterruptedException {
        while (true) {
            for (int i = 0; i < queues.size(); ++i) {
                Queue<M> queue = queues.get(current);
                current = (current+1)%queues.size();
                if (!queue.isEmpty()) {
                    return queue.remove();
                }
            }
            wait();
        }
    }
}

答案 2 :(得分:0)

这就是你如何分配ID。分别将它们 N 分配,其中 N 是生产者的数量,并将每个生产者的索引添加到其中。然后按顺序读取它们将产生循环顺序。你需要做一些记账才能知道何时增加基础ID,当你到达任何 x Nx-1 时会发生这种情况。

答案 3 :(得分:0)

可以使用如下PriorityBlockingQueue在N个生产者之间进行轮循。

为每个生产者维护N个AtomicInteger计数器,并在生产者计数器相等的情况下维护全局计数器作为决胜局。

在为生产者和全局计数器添加到Q增量计数器时,将其存储到Q对象中。 Q的比较器将根据生产者计数器1st进行排序,然后根据存储在Q对象中的全局计数器值进行排序。

但是,当某个生产者的对象在Q中的某个时间为空时,相应的计数器就会落后,并且当对象开始进入时它将开始占用Q。

为避免这种情况,请保留易失性变量,该变量在出队时将使用对象的生产者计数器进行更新。在入队期间增加生产者计数器后,如果该值小于易失变量中最后一个已出队的计数器,则将该计数器重置为该值+1。