Java 使用快速包含(对象o)方法的LinkedBlockingQueue?

Java 使用快速包含(对象o)方法的LinkedBlockingQueue?,java,concurrency,Java,Concurrency,简言之,我正在编写一个应用程序,它需要一个BlockingQueue实现,既提供FIFO添加/删除,又提供一个快速contains方法,我将称之为一个快速方法 LinkedBlockingQueue让我大致了解了这一点,但它的contains方法似乎是以线性时间运行的,因为它基于AbstractQueue的contains方法。我在JavaAPI中没有看到任何东西似乎在宣传一个带有fastcontains开箱即用的LBQ 让事情变得更困难的是,我的项目正处于一个非常严重的时间紧迫期(不,这不是家

简言之,我正在编写一个应用程序,它需要一个
BlockingQueue
实现,既提供FIFO添加/删除,又提供一个快速
contains
方法,我将称之为一个快速
方法

LinkedBlockingQueue
让我大致了解了这一点,但它的
contains
方法似乎是以线性时间运行的,因为它基于
AbstractQueue
contains
方法。我在JavaAPI中没有看到任何东西似乎在宣传一个带有fast
contains
开箱即用的LBQ


让事情变得更困难的是,我的项目正处于一个非常严重的时间紧迫期(不,这不是家庭作业)。我可以在fast
contains
的下面使用一个
HashSet
做一个快速而肮脏的LBQ扩展,但是我仍然需要测试它,这可能会占用大量的工时。我想知道是否有任何受信任的/经过良好测试的库提供了一个
LinkedBlockingQueue
扩展,该扩展带有一个
contains
方法,该方法在O(1)时间内运行。。。?如果没有,欢迎任何其他建议。

正如其他人所说,使用基于哈希的结构是解决方案。 我没有看到Java集合API上的任何实现


但您可以使用Guava类ForwardingBlockingQueue轻松创建HashBlockingQueue装饰器,再次感谢所有提供输入的人。我最终编写了自己的
HashedLinkedBlockingQueue
实现。事实证明,通过使用decorator模式获得正确的同步比预期的要困难得多。向decorator添加同步会导致死锁在重载下发生(特别是,
put(E元素)
take()
引入了以前不存在的保持和等待条件)。再加上由于不必要的同步而导致的性能下降,很明显我需要花时间从头做起

添加
/
删除
/
包含的
中的性能为O(1),但同步成本大约是原始
链接锁定队列的两倍-我说的是“大约两倍”因为LBQ使用两个锁——一个用于插入,一个用于移除,当队列大小足够大时,允许同时修改头部和尾部。我的实现使用一个锁,所以删除必须等待添加完成,反之亦然。这是这个类的源代码——我已经在多线程和单线程模式下测试了每个方法,但是除了在我自己的应用程序中使用这个类之外,我还没有提出一个超级复杂的通用测试。使用风险自负!仅仅因为它在我的应用程序中起作用并不意味着没有bug:

import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.NoSuchElementException;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;

/**
 * Provides a single-lock queuing algorithm with fast contains(Object o) and
 * remove(Object o), at the expense of higher synchronization cost when
 * compared to {@link LinkedBlockingQueue}.  This queue implementation does not
 * allow for duplicate entries.
 * 
 * <p>Use of this particular {@link BlockingQueue} implementation is encouraged
 * when the cost of calling 
 * <code>{@link BlockingQueue#contains(Object o)}</code> or 
 * <code>{@link BlockingQueue#remove(Object o)}</code> outweighs the throughput
 * benefit if using a {@link LinkedBlockingQueue}.  This queue performs best
 * when few threads require simultaneous access to it.
 * 
 * <p>The basic operations this queue provides and their associated run times
 * are as follows, where <i>n</i> is the number of elements in this queue and
 * <i>m</i> is the number of elements in the specified collection, if any such
 * collection is specified:
 * 
 * <ul>
 * <li><b>add(E element)</b> - <i>O(1)</i></li>
 * <li><b>addAll(Collection<? extends E> c)</b> - <i>O(m)</i></li>
 * <li><b>drainTo(Collection<? extends E> c, int maxElements)</b>
 *  - <i>O(maxElements*O(</i><code>c.add(Object o)</code><i>))</i></li>
 * <li><b>contains(E element)</b> - <i>O(1)</i></li>
 * <li><b>offer(E element)</b> - <i>O(1)</i></li>
 * <li><b>poll()</b> - <i>O(1)</i></li>
 * <li><b>remove(E element)</b> - <i>O(1)</i></li>
 * <li><b>removeAll(Collection<? extends E> c)</b> - <i>O(m)</i></li>
 * <li><b>retainAll(Collection<? extends E> c)</b> - <i>O(n*O(
 *  </i><code>c.contains(Object o)</code><i>))</i></li>
 * </ul>
 * 
 * @param <E> type of element this queue will handle.  It is strongly
 * recommended that the underlying element overrides <code>hashCode()</code> 
 * and <code>equals(Object o)</code> in an efficient manner.
 * 
 * @author CodeBlind
 */
@SuppressWarnings("unused")
public class HashedLinkedBlockingQueue<E> implements BlockingQueue<E>{
    /** Polling removes the head, offering adds to the tail. */
    private Node head, tail;
    /** Required for constant-time lookups and removals. */
    private HashMap<E,Node> contents;
    /** Allows the user to artificially limit the capacity of this queue. */
    private final int maxCapacity;

    //Constructors: -----------------------------------------------------------

    /**
     * Creates an empty queue with max capacity equal to
     * {@link Integer#MAX_VALUE}.
     */
    public HashedLinkedBlockingQueue(){ this(null,Integer.MAX_VALUE); }

    /**
     * Creates an empty queue with max capacity equals to the specified value.
     * @param capacity (1 to {@link Integer#MAX_VALUE})
     */
    public HashedLinkedBlockingQueue(int capacity){
        this(null,Math.max(1,capacity)); 
    }

    /**
     * Creates a new queue and initializes it to the contents of the specified
     * collection, queued in the order returned by its iterator, with a max
     * capacity of {@link Integer#MAX_VALUE}.
     * @param c collection of elements to add
     */
    public HashedLinkedBlockingQueue(Collection<? extends E> c){
        this(c,Integer.MAX_VALUE);
    }

    /**
     * Creates a new queue and initializes it to the contents of the specified
     * collection, queued in the order returned by its iterator, with a max
     * capacity equal to the specified value.
     * @param c collection of elements to add
     * @param capacity (1 to {@link Integer#MAX_VALUE})
     */
    public HashedLinkedBlockingQueue(Collection<? extends E> c, int capacity){
        maxCapacity = capacity;
        contents = new HashMap<E,Node>();

        if(c == null || c.isEmpty()){
            head = null;
            tail = null;
        }
        else for(E e : c) enqueue(e);
    }

    //Private helper methods: -------------------------------------------------

    private E dequeue(){
        if(contents.isEmpty()) return null;

        Node n = head;
        contents.remove(n.element);

        if(contents.isEmpty()){
            head = null;
            tail = null;
        }
        else{
            head.next.prev = null;
            head = head.next;
            n.next = null;
        }

        return n.element;
    }

    private void enqueue(E e){
        if(contents.containsKey(e)) return;

        Node n = new Node(e);
        if(contents.isEmpty()){
            head = n;
            tail = n;
        }
        else{
            tail.next = n;
            n.prev = tail;
            tail = n;
        }

        contents.put(e,n);
    }

    private void removeNode(Node n, boolean notify){
        if(n == null) return;
        if(n == head) dequeue();
        else if(n == tail){
            tail.prev.next = null;
            tail = tail.prev;
            n.prev = null;
        }
        else{
            n.prev.next = n.next;
            n.next.prev = n.prev;
            n.prev = null;
            n.next = null;
        }

        contents.remove(n.element);
        if(notify) synchronized(this){ contents.notifyAll(); }
    }

    //Public instance methods: ------------------------------------------------

    public void print(){
        Node n = head;
        int i = 1;
        while(n != null){
            System.out.println(i+": "+n);
            n = n.next;
            i++;
        }
    }

    //Overridden methods: -----------------------------------------------------

    @Override
    public boolean add(E e){
        synchronized(this){
            if(remainingCapacity() < 1) throw new IllegalStateException();
            enqueue(e);
            contents.notifyAll();
        }

        return true;
    }

    @Override
    public boolean addAll(Collection<? extends E> c){
        boolean changed = true;
        synchronized(this){
            for(E e : c){
                if(remainingCapacity() < 1) throw new IllegalStateException();
                enqueue(e);
            }

            contents.notifyAll();
        }
        return changed;
    }

    @Override
    public void clear(){
        synchronized(this){
            if(isEmpty()) return;
            head = null;
            tail = null;
            contents.clear();
            contents.notifyAll();
        }
    }

    @Override
    public boolean contains(Object o){
        synchronized(this){ return contents.containsKey(o); }
    }

    @Override
    public boolean containsAll(Collection<?> c) {
        synchronized(this){
            for(Object o : c) if(!contents.containsKey(o)) return false;
        }

        return true;
    }

    @Override
    public int drainTo(Collection<? super E> c) {
        return drainTo(c,maxCapacity);
    }

    @Override
    public int drainTo(Collection<? super E> c, int maxElements) {
        if(this == c) throw new IllegalArgumentException();
        int transferred = 0;

        synchronized(this){
            while(!isEmpty() && transferred < maxElements)
                if(c.add(dequeue())) transferred++;
            if(transferred > 0) contents.notifyAll();
        }

        return transferred;
    }

    @Override
    public E element(){
        E e = peek();
        if(e == null) throw new IllegalStateException();
        return e;
    }

    @Override
    public boolean isEmpty() {
        synchronized(this){ return contents.isEmpty(); }
    }

    @Override
    public Iterator<E> iterator(){ return new Itr(); }

    @Override
    public boolean offer(E e){
        synchronized(this){
            if(contents.containsKey(e)) return false;
            enqueue(e);
            contents.notifyAll();
        }

        return true;
    }

    @Override
    public boolean offer(E e, long timeout, TimeUnit unit)
            throws InterruptedException{
        long remainingSleep = -1;
        long millis = unit.toMillis(timeout);
        long methodCalled = System.currentTimeMillis();

        synchronized(this){
            while((remainingSleep = 
                    (methodCalled+millis)-System.currentTimeMillis()) > 0 &&
                    (remainingCapacity() < 1 || contents.containsKey(e))){
                contents.wait(remainingSleep);
            }

            if(remainingSleep < 1) return false;
            enqueue(e);
            contents.notifyAll();
        }

        return true;
    }

    @Override
    public E peek(){
        synchronized(this){ return (head != null) ? head.element : null; }
    }

    @Override
    public E poll(){
        synchronized(this){
            E e = dequeue();
            if(e != null) contents.notifyAll();
            return e;
        }
    }

    @Override
    public E poll(long timeout, TimeUnit unit) throws InterruptedException{
        E e = null;
        long remainingSleep = -1;
        long millis = unit.toMillis(timeout);
        long methodCalled = System.currentTimeMillis();

        synchronized(this){
            e = dequeue();

            while(e == null && (remainingSleep = (methodCalled+millis)-
                    System.currentTimeMillis()) > 0){
                contents.wait(remainingSleep);
                e = dequeue();
            }

            if(e == null) e = dequeue();
            if(e != null) contents.notifyAll();
        }

        return e;
    }

    @Override
    public void put(E e) throws InterruptedException{
        synchronized(this){
            while(remainingCapacity() < 1) contents.wait();
            enqueue(e);
            contents.notifyAll();
        }
    }

    @Override
    public int remainingCapacity(){ return maxCapacity-size(); }

    @Override
    public E remove(){
        E e = poll();
        if(e == null) throw new IllegalStateException();
        return e;
    }

    @Override
    public boolean remove(Object o){
        synchronized(this){
            Node n = contents.get(o);
            if(n == null) return false;
            removeNode(n,true);
        }

        return true;
    }

    @Override
    public boolean removeAll(Collection<?> c){
        if(this == c){
            synchronized(this){
                if(isEmpty()){
                    clear();
                    return true;
                }
            }

            return false;
        }

        boolean changed = false;

        synchronized(this){
            for(Object o : c){
                Node n = contents.get(o);
                if(n == null) continue;
                removeNode(n,false);
                changed = true;
            }

            if(changed) contents.notifyAll();
        }

        return changed;
    }

    @Override
    public boolean retainAll(Collection<?> c){
        boolean changed = false;
        if(this == c) return changed;

        synchronized(this){
            for(E e : new LinkedList<E>(contents.keySet())){
                if(!c.contains(e)){
                    Node n = contents.get(e);
                    if(n != null){
                        removeNode(n,false);
                        changed = true;
                    }
                }
            }

            if(changed) contents.notifyAll();
        }

        return changed;
    }

    @Override
    public int size(){ synchronized(this){ return contents.size(); }}

    @Override
    public E take() throws InterruptedException{
        synchronized(this){
            while(contents.isEmpty()) contents.wait();
            return dequeue();
        }
    }

    @Override
    public Object[] toArray(){
        synchronized(this){ return toArray(new Object[size()]); }
    }

    @SuppressWarnings("unchecked")
    @Override
    public <T> T[] toArray(T[] a) {
        synchronized(this){
            //Estimate size of array; be prepared to see more or fewer elements
            int size = size();
            T[] r = a.length >= size ? a :
                (T[])java.lang.reflect.Array
                .newInstance(a.getClass().getComponentType(), size);
            Iterator<E> it = iterator();

            for (int i = 0; i < r.length; i++) {
                if (! it.hasNext()) { // fewer elements than expected
                    if (a != r)
                        return Arrays.copyOf(r, i);
                    r[i] = null; // null-terminate
                    return r;
                }
                r[i] = (T)it.next();
            }
            return it.hasNext() ? finishToArray(r, it) : r;
        }
    }

    //Static helper methods: --------------------------------------------------

    @SuppressWarnings("unchecked")
    private static <T> T[] finishToArray(T[] r, Iterator<?> it) {
        int i = r.length;

        while (it.hasNext()) {
            int cap = r.length;
            if (i == cap) {
                int newCap = ((cap / 2) + 1) * 3;
                if (newCap <= cap) { // integer overflow
                    if (cap == Integer.MAX_VALUE)
                        throw new OutOfMemoryError
                        ("Required array size too large");
                    newCap = Integer.MAX_VALUE;
                }
                r = Arrays.copyOf(r, newCap);
            }
            r[i++] = (T)it.next();
        }
        // trim if overallocated
        return (i == r.length) ? r : Arrays.copyOf(r, i);
    }

    //Private inner classes: --------------------------------------------------

    /**
     * Provides a weak iterator that doesn't check for concurrent modification
     * but also fails elegantly.  A race condition exists when simultaneously
     * iterating over the queue while the queue is being modified, but this is
     * allowable per the Java specification for Iterators.
     * @author CodeBlind
     */
    private class Itr implements Iterator<E>{
        private Node current;
        private E currentElement;

        private Itr(){
            synchronized(HashedLinkedBlockingQueue.this){
                current = head;
                if(current != null) currentElement = current.element;
                else currentElement = null;
            }
        }

        @Override
        public boolean hasNext(){
            return currentElement != null;
        }

        @Override
        public E next(){
            if(currentElement == null) throw new NoSuchElementException();

            synchronized(HashedLinkedBlockingQueue.this){
                E e = currentElement;

                current = current.next;
                if(current == null || !contents.containsKey(current.element)){
                    current = null;
                    currentElement = null;
                }
                else currentElement = current.element;

                return e;
            }
        }

        @Override
        public void remove(){
            synchronized(HashedLinkedBlockingQueue.this){
                if(current == null || !contents.containsKey(current.element))
                    throw new NoSuchElementException();

                Node n = current;
                current = current.next;
                if(current != null && contents.containsKey(current.element))
                    currentElement = current.element;
                else currentElement = null;

                removeNode(n,true);
            }
        }
    }

    /**
     * This class provides a simple implementation for a node in a double-
     * linked list.  It supports constant-time, in-place removals.
     * @author CodeBlind
     */
    private class Node{
        private Node(E e){
            element = e;
            prev = null;
            next = null;
        }

        private E element;
        private Node prev, next;

        @Override
        public String toString(){
            StringBuilder sb = new StringBuilder("Node[prev.element=");
            if(prev == null) sb.append("null,element=");
            else sb.append(prev.element+",element=");
            sb.append(element+",next.element=");
            if(next == null) sb.append("null]");
            else sb.append(next.element+"]");
            return sb.toString();
        }
    }
}

嗯。。。您预计使用额外的哈希集测试队列需要多少工时?叫我疯狂先生很危险,但对我来说,这听起来并不是一项重要的工作……那你就不能在政府机构工作了……;)是的--实际上,您可能最终会得到的哈希映射为。一种肮脏的方法是并行维护类似哈希集的结构(例如concurrentmap),但将所有内容原子化将引入不必要的同步/争用。如果要使contains()快速,则代价是插入和删除速度较慢。