我有一个用于缓冲数据的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()方法在该arraylist上被调用,同时正在进行转换会是一个问题吗?
我有时会得到索引超出范围的异常,它的类似于: index:100 size 300,这是奇怪的cuz索引在大小范围内,所以我想知道这是否可能导致问题,或者我应该寻找其他问题。
谢谢
答案 0 :(得分: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);
}
即使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!
}
这样效率较低,代码比BlockingQueue
更多,因此没有理由这样做。
答案 1 :(得分:2)
是的,会出现问题,因为ArrayList不是线程安全的,ArrayList对象的内部状态会被破坏,最终会出现一些不正确的输出或运行时异常。您可以尝试使用synchronizedList(List list),或者如果它很合适,您可以尝试使用CopyOnWriteArrayList。
此问题是Producer–consumer problem。您可以通过使用某种类型的锁定从缓冲区中提取对象(在您的情况下为List)来查看有多少人修复它。如果您不一定需要List,也可以查看线程安全缓冲区实现。