以FIFO顺序固定容量的线程安全收集

时间:2016-06-09 15:56:33

标签: java multithreading collections

问题:维护一个具有固定容量的集合(比如2个元素),可以同时访问100多个线程。

始终存储最近线程中的最新元素。存储后,编写一个方法来检查所有这些元素是否重复。

我的解决方案:BlockingQueue具有固定容量并实现自定义添加方法。

import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.Iterator;

public class FixedBlockingQueue<T extends Object> {

    final BlockingQueue<T> queue;
    private int capacity;

    public FixedBlockingQueue(int capacity){
        super();
        this.capacity = capacity;
        queue = new ArrayBlockingQueue<T>(capacity);
        System.out.println("Capactiy:"+this.capacity);
    }
    public void addElement(T element){
        try{
            if ( queue.size() > capacity - 1 ){
                queue.remove();         
            }
            queue.put(element);
            for ( Iterator it = queue.iterator(); it.hasNext();){
                System.out.println(it.next());
            }
            System.out.println("________");
        }catch(Exception err){
            err.printStackTrace();
        }
    }

    public static void main(String args[]){
        FixedBlockingQueue<Integer> f = new FixedBlockingQueue<Integer>(2);
        for ( int i=0; i< 10; i++){
            f.addElement(i);
        }

    }   
}

输出:

0
________
0
1
________
1
2
________
2
3
________
3
4
________
4
5
________
5
6
________
6
7
________
7
8
________
8
9

从输出中,您可以清楚地看到第一个元素被删除,最近的元素被添加到队列中。

我的疑问:这是一个好的解决方案吗?还是有其他好的解决方案,比这更好吗?

编辑:在频繁删除的情况下,ArrayBlockingQueue优于LinkedBlockingQueue

2 个答案:

答案 0 :(得分:6)

让我们避免重新发明轮子,只需使用具有固定容量的LinkedBlockingQueue,它就是一个线程安全的FIFO BlockingQueue。更多详情here

您的代码存在的问题是您不是以原子方式执行以下操作,以免您遇到竞争条件问题:

if ( queue.size() > capacity - 1 ){
    queue.remove();         
}
queue.put(element);

您需要将其包装到synchronized块中或使用显式Lock来保护它,因为它是一个关键部分,我们不希望多个线程同时调用它。< / p>

以下是如何使用BlockingQueue

完成的操作
BlockingQueue queue = new LinkedBlockingQueue(2);
for ( int i=0; i< 10; i++){
    // Try to add the object and return immediately if it is full
    // then if it could not be added,
    // remove the last element of the queue and try again
    while (!queue.offer(i, 0L, TimeUnit.MICROSECONDS)) {
        queue.remove();
    }
    for ( Iterator it = queue.iterator(); it.hasNext();){
        System.out.println(it.next());
    }
    System.out.println("________");
}

<强>输出:

0
________
0
1
________
1
2
________
2
3
________
3
4
________
4
5
________
5
6
________
6
7
________
7
8
________
8
9
________

答案 1 :(得分:3)

我首先要承认我从未使用过并发包中的BlockingQueue,但之前我已经完成了多线程编程。

我相信这里有一个问题:

now = arrow.now()
oneDayFromNow = now.replace(days+=1)

如果有多个线程同时运行此方法,那么多个线程可以执行此检查,并且可以在它们采取操作之前为其中的大量线程评估为true。因此,在这种情况下,if ( queue.size() > capacity - 1 ){ queue.remove(); } 的调用次数可能超出预期。

基本上,如果你想保持逻辑,那么你必须找到一种方法来确保另一个线程在你检查大小和之间的时间之间改变队列的大小是不可能的。然后对它执行操作,例如删除元素。

解决此问题的一种方法可能是将其包装在同步块中,如下所示:

remove()

这可确保在检查其当前大小后,synchornized (queue) { if ( queue.size() > capacity - 1 ){ queue.remove(); } queue.put(element); for ( Iterator it = queue.iterator(); it.hasNext();){ System.out.println(it.next()); } System.out.println("________"); } 无法被其他线程访问。请记住,您已经同步的内容越多,其他线程在对其执行操作之前必须等待的时间越长,这可能会降低您的程序速度。您可以详细了解此关键字here