如何在ArrayList上实现安全的读/写并发?

时间:2014-04-03 16:18:47

标签: java arraylist concurrency thread-safety

我的应用程序设计如下:

  1. 数据连续附加到ArrayList(通过多次写入)
  2. 读取器线程从列表的开头(保证读取受当前写入影响的索引之前)读取数据。
  3. 是否有一种线程安全的方式来实现它,以便:

    • 写入线程在写入时相互阻塞(以避免写入并发问题)

    • 当其他读者阅读

    • 时,读取器线程不会被阻止
    • 当编写器线程附加到结尾时,读取器线程不会被阻止

      我在这里假设只是写入ArrayList的末尾对于数组的读者来说是线程安全的

    • 但是,在写入一些会导致读取线程不安全的操作时,读取线程被阻止 - 如果在追加期间阵列耗尽已分配的内存,则重新分配完整的数组数据。

      我在这里假设重新分配ArrayList的内存对于数组启动的读者来说不是线程安全的 - 如果数组增长实际上是通过添加更多链接数组来实现的更改可能是错误假设的原始数组


    UPDATE :最后,我只是将整个系统重新设计为一个数组数组,而不是解决这个问题。这样我只能在创建 - 或者更确切地说,添加 - 新子数组时锁定父数组;并且任何其他锁都在各个子阵列上,无论如何都被程序中的所有操作原子地处理,因此不会受到锁定问题的困扰。

3 个答案:

答案 0 :(得分:2)

  

如何在ArrayList上实现安全的读/写并发?

很难弄清楚列表实际发生了什么。在读者从前面消费的同时,项目正在被添加,但我不知道是否有多个读者阅读单个项目或者每个读者是否消耗了每个元素?

但无论如何,我会考虑转而使用像BlockingQueue这样的ArrayBlockingQueueArrayList根本不是为您的用例而构建的。我会为工作提供BlockingQueue,然后为结果提供另一个BlockingQueue。同样,这假设列表中的条目被多个线程使用。

这里的主要问题之一是同步性能有两个功能:互斥保护和内存同步。作者必须锁定,以便它们不会发生冲突,但读者需要锁定才能看到作者对列表的更新。

就阻止细节而言,由于动态扩展列表涉及竞争条件,因此无法满足您的一些要求。

  

我在这里假设只是写入ArrayList的末尾对于数组启动的读者来说是线程安全的

除非写入列表末尾导致重新分配列表,否则这是正确的。但是,您需要同步列表以查看编写者对列表所做的任何更新。即使您的ArrayListvolatile,列表中的字段也不是。{/ p>

  

我在这里假设重新分配ArrayList的内存对于数组启动的读者来说不是线程安全的......

不,不是。在涉及数据数组时,ArrayList的任何部分都不是线程安全的。如果有任何问题,我建议查看来源。从add(...)和任何其他更改列表大小的方法调用以下方法。

public void ensureCapacity(int minCapacity) {
    modCount++;
    int oldCapacity = elementData.length;
    if (minCapacity > oldCapacity) {
        Object oldData[] = elementData;
        int newCapacity = (oldCapacity * 3)/2 + 1;
        if (newCapacity < minCapacity)
            newCapacity = minCapacity;
        // minCapacity is usually close to size, so this is a win:
        elementData = Arrays.copyOf(elementData, newCapacity);
    }
}

答案 1 :(得分:1)

我不确定你为什么要使用ArrayList。您所描述的是queueFirst-In-First-Out数据结构。 Java提供了几个线程安全的Queue实现,特别是ConcurrentLinkedQueueLinkedBlockingQueue

ArrayList基本上不是线程安全的,唯一的方法就是将它包装在对Collections.synchronizedList()的调用中,这将使对数组的读写访问基本上是单一的 - 螺纹的。

答案 2 :(得分:0)

考虑情景:

Reader R1获取列表并使用它

Writer W1将一个元素追加到列表中,而不进行数组重新分配

Reader R2获取列表并使用它。

问题是:R1和R2看到的列表对象应该不同,还是可以相同?在第一种情况下,每个写操作都必须创建新对象,尽管底层数组可以是相同的(这比使用CopyOnWriteArrayList更有效)。在第二种情况下,R1可以看到列表的长度不可预测地增长,这可能会导致编程错误。

在这两种情况下,AtomicReference都可用于存储列表的当前版本。读者只需从中获取当前列表,编写者使用监视器的其他对象来同步更新操作。