我有一个Arraylist,我不断添加和删除单独的线程。 一个线程添加,另一个删除。
这是包含更改列表的类:
public class DataReceiver {
private static final String DEBUG_TAG = "DataReceiver";
// Class variables
private volatile ArrayList<Byte> buffer;
//private volatile Semaphore dataAmount;
public DataReceiver() {
this.buffer = new ArrayList<Byte>();
//this.dataAmount = new Semaphore(0, true);
}
// Adds a data sample to the data buffer.
public final void addData(byte[] newData, int bytes) {
int newDataPos = 0;
// While there is still data
while(newDataPos < bytes) {
// Fill data buffer array with new data
buffer.add(newData[newDataPos]);
newDataPos++;
//dataAmount.release();
}
return;
}
public synchronized byte getDataByte() {
/*
try {
dataAmount.acquire();
}
catch(InterruptedException e) {
return 0;
}
*/
while(buffer.size() == 0) {
try {
Thread.sleep(250);
}
catch(Exception e) {
Log.d(DEBUG_TAG, "getDataByte: failed to sleep");
}
}
return buffer.remove(0);
}
}
问题是我在尝试buffer.remove(0)
时经常遇到一个空指针。正如您可以在代码中告诉表单中的注释,我尝试在某一点使用信号量,但它仍然间歇地抛出nullpointer异常,因此我创建了自己的sleep-poll类型作为概念的半证明。
我不明白为什么会出现空指针异常和/或如何修复它。
答案 0 :(得分:1)
如果你在另一个线程中处理对象初始化,那么构造函数可能在
之前没有完成 public synchronized byte getDataByte()
因此调用导致NullPointerException
因为
this.buffer = new ArrayList<Byte>();
从未被召唤过。
答案 1 :(得分:0)
我猜到了一个解释。我会在评论中这样做,但我没有足够的声誉,所以希望这个答案是有帮助的。
首先,如果你要将addData()函数声明为synchronized,你的问题会消失吗?我的猜测是它会。
我的理论是,尽管您将缓冲区声明为volatile,但这并不足以保护您的用例。想象一下这个案例:
我的理论是buffer.add()不是原子操作。在buffer.add()操作期间的某个地方,它的内部大小计数器递增,使得对buffer.size()== 0的getDataByte()调用返回false。有时,getDataByte()会在buffer.add()调用完成之前继续调用buffer.remove()。
这是基于我在这里阅读的摘录: https://www.ibm.com/developerworks/java/library/j-jtp06197/ “虽然递增操作(x ++)可能看起来像一个操作,但它实际上是一个必须以原子方式执行的复合读 - 修改 - 写操作序列 - 而且volatile不提供必要的原子性。”