多个线程访问ArrayList

5

我有一个ArrayList用于缓存数据,以便其他线程可以读取它们。

由于它从UDP源读取,因此这个数组不断地添加数据,而其他线程不断地从该数组中读取。然后从数组中删除数据。

这不是实际的代码,而是一个简化的例子:

 public class PacketReader implements Runnable{

   pubic static ArrayList<Packet> buffer = new ArrayList() ;
    @Override 
    public void run(){
    while(bActive){
    //read from udp source and add data to the array
  }
 }

 public class Player implements Runnable(){

 @Override 
 public void run(){


 //read packet from buffer
 //decode packets

 // now for the problem :

 PacketReader.buffer.remove(the packet that's been read);
     }
   }

remove() 方法从数组中删除数据包,然后将右侧的所有数据包向左移动以覆盖空白位置。

我的担忧是:由于缓冲区不断地被多个线程添加和读取,那么 remove() 方法会出现问题吗?因为它需要将数据包向左移动。

我的意思是,如果在进行移动的同时调用了 .add() 或 .get() 方法,这会成为一个问题吗?

有时候我会得到“index out of bounds”异常,例如:索引为100、大小为300,这很奇怪,因为索引在大小之内,所以我想知道是否可能是这个问题导致的,还是应该寻找其他问题。

谢谢。


可能是Best Java thread-safe locking mechanism for collections?的重复问题。 - aruisdante
基本上,如果您使用迭代器,则会从中得到“ConcurrentModificationException”。 最好完全使用其他东西。 - markspace
我从未遇到过这个错误,这就让我感到困惑了...我猜这不是问题的原因? - vlatkozelka
ConcurrentModificationException 是多个访问结构的症状,这些访问被组织得足够好,以至于实现可以检测到它们。对于这种不受控制的并行访问,我预计会出现错误的答案。 - Patricia Shanahan
2个回答

7
看起来你真正需要的是一个 BlockingQueueArrayBlockingQueue 可能是一个不错的选择。如果你需要一个无限队列并且不关心额外的内存利用率(相对于 ArrayBlockingQueue),LinkedBlockingQueue 也可以使用。
它让你以线程安全和高效的方式推入和弹出项目。这些推入和弹出的行为可能会有所不同(当你尝试向已满队列推入或从空队列弹出时会发生什么?),BlockingQueue 接口的 JavaDocs 有一张表格很好地展示了所有这些行为。

一个线程安全的List(无论它来自synchronizedList还是CopyOnWriteArrayList),实际上是不够的,因为您的用例使用了经典的“检查-然后-操作”模式,这本质上是有竞争的。考虑以下代码片段:

if(!list.isEmpty()) {
    Packet p = list.remove(0); // remove the first item
    process(p);
}

即使list是线程安全的,但这种用法并不安全!如果在“if”检查期间list只有一个元素,但在您执行remove(0)之前另一个线程将其删除了,该怎么办?
您可以通过在两个操作周围进行同步来解决此问题:
Pattern p;
synchronized (list) {
    if (list.isEmpty()) {
        p = null;
    } else {
        p = list.remove(0);
    }
}
if (p != null) {
    process(p);  // we don't want to call process(..) while still synchronized!
}

这种方法比使用 BlockingQueue 更低效,需要更多的代码,因此没有理由这样做。


同意,从问题描述来看,这似乎是最佳解决方案。 - Leon
3
用 ArrayBlockingQueue 替换 ArrayList,并用 put() 和 take() 替换 add() 和 remove()... 可以减少 CPU 和内存的使用,且不会崩溃 :) - vlatkozelka

3

是的,如果使用ArrayList会有问题,因为它不是线程安全的,ArrayList对象的内部状态会被破坏,最终会出现一些不正确的输出或运行时异常。您可以尝试使用synchronizedList(List list),或者如果适合您的情况,可以尝试使用CopyOnWriteArrayList

这个问题是生产者-消费者问题。您可以看到,人们通过使用某种锁定方法轮流从缓冲区(在您的情况下是一个列表)中提取对象来解决这个问题。如果您不一定需要一个列表,还可以查看线程安全的缓冲区实现。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接