1

我有一个 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). 正如您可以从代码中的注释中看出的那样,我曾尝试使用信号量,但它仍然间歇性地抛出空指针异常,因此我创建了自己的睡眠轮询类型作为半概念验证。

我不明白为什么会发生空指针异常和/或如何修复它。

4

2 回答 2

1

如果您在不同的线程中处理对象初始化,则构造函数可能在

 public synchronized byte getDataByte() 

被称为因此导致NullPointerException因为

this.buffer = new ArrayList<Byte>(); 

从未被调用。

于 2013-10-30T19:45:14.107 回答
0

我有一个解释的猜测。我会在评论中这样做,但我没有足够的声誉,所以希望这个答案会有所帮助。

首先,如果您将 addData() 函数声明为同步,您的问题会消失吗?我的猜测是会的。

我的理论是,尽管您将缓冲区声明为易失性,但这并不足以保护您的用例。想象一下这种情况:

  • addData() 被调用并正在调用 buffer.add()
  • 同时,getDataByte() 正在检查 buffer.size() == 0

我的理论是 buffer.add() 不是原子操作。在 buffer.add() 操作期间的某个地方,它的内部大小计数器递增,使您的 getDataByte() 调用 buffer.size() == 0 返回 false。有时,在您的 buffer.add() 调用完成之前,getDataByte() 会继续其 buffer.remove() 调用。

这是基于我在此处阅读的摘录: https ://www.ibm.com/developerworks/java/library/j-jtp06197/ “虽然增量操作 (x++) 可能看起来像单个操作,但它实际上是一个复合操作必须以原子方式执行的读-修改-写操作序列——而 volatile 不提供必要的原子性。”

于 2013-10-30T21:54:55.433 回答