Java 向ArrayBlockingQueue添加功能

Java 向ArrayBlockingQueue添加功能,java,multithreading,Java,Multithreading,我正在尝试向ArrayBlockingQueue添加功能,特别是我希望队列只保留唯一的元素,即如果条目已经包含在队列中,则不将其排队。 由于我想要的功能与JCIP第4.4项中Vector的扩展相同,所以我尝试使用那里的方法实现它 通过扩展实现不起作用,因为ArrayBlockingQueue使用包私有ReentrantLock实现其互斥,因此作为扩展类,我无法获得对它的引用。这将是一个脆弱的方法,即使它确实有效 客户端锁定的实现不起作用,因为不支持客户端锁定 一开始,通过组合实现似乎是一种可行

我正在尝试向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...
    }
    
    public类DistinctBlockingQueue实现BlockingQueue{
    私有最终阻塞队列backingQueue;
    public DistinctBlockingQueue(阻塞队列backingQueue){
    this.backingQueue=backingQueue;
    }
    @凌驾
    公共同步布尔报价(E){
    if(backingQueue.contains(e)){
    返回false;
    }
    返回backingQueue.offer(e);
    }
    @凌驾
    public synchronized E take()引发InterruptedException{
    return backingQueue.take();
    }
    //其他方法。。。
    }
    
    不幸的是,在组成ArrayBlockingQueue时,这种方法会在以下简单场景中产生死锁:

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

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

也许一个简单而快速的解决方案是使用
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

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

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

}
import java.util.concurrent.BlockingQueue;
导入java.util.concurrent.ConcurrentHashMap;
导入java.util.concurrent.ConcurrentMap;
公共类DistinctBlockingQueue实现BlockingQueue{
私有最终阻塞队列backingQueue;
私有最终ConcurrentMap元素=新ConcurrentHashMap();
public DistinctBlockingQueue(阻塞队列backingQueue){
this.backingQueue=backingQueue;
}
@凌驾
公共布尔报价(E){
布尔[]加法={false};
elements.ComputeFabSent(e,k->add[0]=true);
如果(添加[0]){
//确保已将元素添加到队列中,
//否则我们必须把它从地图上删除
if(backingQueue.offer(e)){
返回true;
}
删除(e)项;
}
返回false;
}
@凌驾
public E take()抛出InterruptedException{
E=backingQueue.take();
删除(e)项;
返回e;
}
@凌驾
公共字符串toString(){
返回backingQueue.toString();
}
//其他方法
}
而且。。。让我们做一些并发测试:

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]
BlockingQueue=newdistinctblockingqueue(newarrayblockingqueue(100));
int n=1000;
ExecutorService producerService=Executors.newFixedThreadPool(n);
可调用生产者=()->{
排队。要约(“a”);
返回null;
};
invokeAll(IntStream.range(0,n).mapToObj(i->producer.collector)(Collectors.toList());
producerService.shutdown();
System.out.println(队列);//印刷品[a]

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

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...
}
public类DistinctBlockingQueue实现BlockingQueue{
私有最终阻塞队列backingQueue;
私有最终集entriesSet=ConcurrentHashMap.newKeySet();
public DistinctBlockingQueue(阻塞队列backingQueue){
this.backingQueue=backingQueue;
entriesSet.addAll(backingQueue);
}
@凌驾
公共布尔报价(E){
如果(!entriesSet.add(e))
返回false;
布尔添加=backingQueue.offer(e);
如果(!已添加){
entriesSet.移除(e);
}
增加了退货;
}
@凌驾
public E take()抛出InterruptedException{
E=backingQueue.take();
entriesSet.移除(e);
返回e;
}
//其他方法。。。
}
额外的设置不是问题,因为我无论如何都想使用一个,以获得合理的性能

但是,我可以想到这个实现的一个问题,如果它与有界队列实现(如ArrayBlockingQueue)结合使用,集合将不会有界,因此当有许多提供被阻止时,集合可能会变得非常大


此解决方案划分了一个显然应该是原子操作的操作,因此我高度怀疑我忽略了其他问题。

谢谢您的回答,但您的解决方案不太好。当两个线程试图提供相同的项目时,就会出现问题。假设第一个线程到达返回语句并切换上下文,那么第二个线程开始运行,当它到达返回语句时,将提供e,然后当第一个线程重新调度时,将再次提供e。我们必须确保映射和队列不会在缺席支票和报价之间被修改,这就是为什么我们必须使用锁,或者更聪明的同步方法。@DLevant这永远不会发生
computeIfAbsent
会起作用。。。请参阅更新。在第一个线程上将add[0]设置为true之后,没有任何东西可以阻止对另一个线程进行调度,在这种情况下,相同的元素可以添加两次。像这样的测试很难实现
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...
}