向ArrayBlockingQueue添加功能

时间:2016-05-23 06:23:25

标签: java multithreading

我正在尝试向ArrayBlockingQueue添加功能,特别是我希望队列只保留唯一元素,即如果条目已经包含在队列中,则不会将条目排入队列。 由于我想要的功能与JCIP第4.4项中Vector的扩展相同,我尝试使用那里的方法实现它。

  • 通过扩展实现不起作用,因为ArrayBlockingQueue使用包私有ReentrantLock实现其互斥,因此作为扩展类我无法获得对它的引用。即使它确实有效,这也是一种脆弱的方法。
  • 客户端锁定的实现不起作用,因为没有客户端锁定支持。
  • 组合实现似乎是最初的方法,产生代码,如

    public class DistinctBlockingQueue<E> implements BlockingQueue<E> {
        private final BlockingQueue<E> backingQueue;
    
        public DistinctBlockingQueue(BlockingQueue<E> backingQueue) {
            this.backingQueue = backingQueue;
        }
    
        @Override
        public synchronized boolean offer(E e) {
            if (backingQueue.contains(e)) {
                return false;
            }
    
            return backingQueue.offer(e);
        }
    
        @Override
        public synchronized E take() throws InterruptedException {
            return backingQueue.take();
        }
    
        // Other methods...
    }
    

    不幸的是,在编写ArrayBlockingQueue时,这种方法会在以下简单场景中产生死锁:

    1. 线程A调用take()并获取同步锁和ArrayBlockingQueue的内部锁。
    2. 线程A看到队列为空时阻塞并释放ArrayBlockingQueue的内部锁。
    3. 线程B使用元素调用offer(),但无法获取同步锁,永远阻塞。

我的问题是,如何在不重写ArrayBlockingQueue的情况下实现此功能?

2 个答案:

答案 0 :(得分:4)

也许一个简单而快速的解决方案是使用java.util.concurrent.ConcurrentMap

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class DistinctBlockingQueue<E> implements BlockingQueue<E> {

    private final BlockingQueue<E> backingQueue;
    private final ConcurrentMap<E, Boolean> elements = new ConcurrentHashMap<>();

    public DistinctBlockingQueue(BlockingQueue<E> backingQueue) {
        this.backingQueue = backingQueue;
    }

    @Override
    public boolean offer(E e) {
        boolean[] add = {false};
        elements.computeIfAbsent(e, k -> add[0] = true);
        return add[0] && backingQueue.offer(e);
    }

    @Override
    public E take() throws InterruptedException {
        E e = backingQueue.take();
        elements.remove(e);
        return e;
    }

    // Other methods

}

请注意,不需要同步

编辑

java.util.concurrent.ConcurrentHashMap处的文档说:

/**
 * If the specified key is not already associated with a value,
 * attempts to compute its value using the given mapping function
 * and enters it into this map unless {@code null}.  The entire
 * method invocation is performed atomically, so the function is
 * applied at most once per key.  Some attempted update operations
 * on this map by other threads may be blocked while computation
 * is in progress, so the computation should be short and simple,
 * and must not attempt to update any other mappings of this map.
 *
 * @param key key with which the specified value is to be associated
 * @param mappingFunction the function to compute a value
 * @return the current (existing or computed) value associated with
 *         the specified key, or null if the computed value is null
 * @throws NullPointerException if the specified key or mappingFunction
 *         is null
 * @throws IllegalStateException if the computation detectably
 *         attempts a recursive update to this map that would
 *         otherwise never complete
 * @throws RuntimeException or Error if the mappingFunction does so,
 *         in which case the mapping is left unestablished
 */
public V computeIfAbsent(K key, Function<? super K, ? extends V> mappingFunction) {
    ...
}

我添加了一些额外的检查:

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

public class DistinctBlockingQueue<E> implements BlockingQueue<E> {

    private final BlockingQueue<E> backingQueue;
    private final ConcurrentMap<E, Boolean> elements = new ConcurrentHashMap<>();

    public DistinctBlockingQueue(BlockingQueue<E> backingQueue) {
        this.backingQueue = backingQueue;
    }

    @Override
    public boolean offer(E e) {
        boolean[] add = {false};
        elements.computeIfAbsent(e, k -> add[0] = true);
        if (add[0]) {
            // make sure that the element was added to the queue,
            // otherwise we must remove it from the map
            if (backingQueue.offer(e)) {
                return true;
            }
            elements.remove(e);
        }
        return false;
    }

    @Override
    public E take() throws InterruptedException {
        E e = backingQueue.take();
        elements.remove(e);
        return e;
    }

    @Override
    public String toString() {
        return backingQueue.toString();
    }

    // Other methods

}

和...让我们做一些并发测试:

BlockingQueue<String> queue = new DistinctBlockingQueue<>(new ArrayBlockingQueue<>(100));

int n = 1000;
ExecutorService producerService = Executors.newFixedThreadPool(n);

Callable<Void> producer = () -> {
    queue.offer("a");
    return null;
};

producerService.invokeAll(IntStream.range(0, n).mapToObj(i -> producer).collect(Collectors.toList()));
producerService.shutdown();

System.out.println(queue); // prints [a]

答案 1 :(得分:1)

我找到了一个问题的部分答案。提供操作不是我想要的原子操作,但是队列是不同的。

public class DistinctBlockingQueue<E> implements BlockingQueue<E> {
    private final BlockingQueue<E> backingQueue;
    private final Set<E> entriesSet = ConcurrentHashMap.newKeySet();

    public DistinctBlockingQueue(BlockingQueue<E> backingQueue) {
        this.backingQueue = backingQueue;
        entriesSet.addAll(backingQueue);
    }

    @Override
    public boolean offer(E e) {
        if (!entriesSet.add(e))
            return false;

        boolean added = backingQueue.offer(e);
        if (!added) {
            entriesSet.remove(e);
        }

        return added;
    }

    @Override
    public E take() throws InterruptedException {
        E e = backingQueue.take();
        entriesSet.remove(e);

        return e;
    }

    // Other methods...
}

额外的Set不是问题,因为我想要使用一个以获得合理的性能。

但是,我可以想到这个实现的一个问题,如果它与有界队列实现(例如ArrayBlockingQueue)一起使用,则该集合将不受限制,因此当有许多优惠时,该集合可能会变得非常大阻止。

这个解决方案将一个明显属于原子的操作分开,所以我非常怀疑应该忽略其他问题。