【问题标题】:List concurrency failing列出并发失败
【发布时间】:2013-10-30 19:09:21
【问题描述】:

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

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

【问题讨论】:

  • 您需要先添加数据,然后再将其删除。 size() 仅指示列表中存在的元素数。如果列表中不存在元素,则无法删除它们。
  • 为什么不只使用java.util.concurrent 类之一?看起来LinkedBlockingDeque 应该完全符合您的要求。
  • 你能添加暴露问题的客户端代码吗?
  • 请分享异常的堆栈跟踪。
  • 在同步方法中休眠似乎不是一个好主意。

标签: java android multithreading list concurrency


【解决方案1】:

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

 public synchronized byte getDataByte() 

因此被调用导致NullPointerException,因为

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

从未被调用。

【讨论】:

  • +1 :这是最可能的原因,但是如果没有堆栈跟踪和客户端代码,确实不可能确定确切的原因。
【解决方案2】:

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

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

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

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

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

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

【讨论】:

    猜你喜欢
    • 2016-07-30
    • 2016-12-29
    • 2021-03-10
    • 1970-01-01
    • 1970-01-01
    • 2023-03-26
    • 2018-08-31
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多