多个线程访问ArrayList

vla*_*lka 3 java multithreading arraylist

我有一个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);
     }
   }
Run Code Online (Sandbox Code Playgroud)

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

我担心的是:由于缓冲区经常被多个线程添加和读取,因此remove()方法会产生问题,因为它必须将数据包移到左边?

我的意思是如果.add()或.get()方法在该arraylist上被调用,同时正在进行转换会是一个问题吗?

我有时会得到索引超出范围的异常,它的类似于:index:100 size 300,这是奇怪的cuz索引在大小范围内,所以我想知道这是否可能导致问题或我应该寻找其他问题 .

谢谢

ysh*_*vit 6

这听起来像你真正想要的是一个BlockingQueue.ArrayBlockingQueue可能是个不错的选择.如果您需要一个无界的队列而不关心额外的内存利用率(相对于ArrayBlockingQueue),LinkedBlockingQueue也可以.

它允许您以线程安全且高效的方式将项目推入并弹出.那些推送和弹出的行为可能有所不同(当你试图推送到一个完整的队列时会发生什么,或者从一个空队列弹出?),并且BlockingQueue接口的JavaDocs 有一个表格可以很好地显示所有这些行为.


线程安全List(无论它来自synchronizedList或来自CopyOnWriteArrayList)实际上是不够的,因为您的用例使用经典的check-then-act模式,而且本质上是活泼的.请考虑以下代码段:

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

即使list是线程安全的,这种用法也不是!如果list在"if"检查期间有一个元素,但是在你到达之前另一个线程将其删除,该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!
}
Run Code Online (Sandbox Code Playgroud)

但这样效率较低,需要的代码多于a BlockingQueue,因此没有理由这样做.

  • 用 ArrayBlockingQueue 替换了 ArrayList,用 put() take() 替换了 ArrayList 和 add()/remove() ...更少的 CPU 和内存使用,并且没有崩溃:) (2认同)