【问题标题】:Concurrent ArrayList并发数组列表
【发布时间】:2014-12-12 23:29:21
【问题描述】:

我需要一个类似 ArrayList 的结构,只允许以下操作

  • get(int index)
  • add(E element)
  • set(int index, E element)
  • iterator()

由于很多地方都使用了迭代器,所以使用Collections#synchronizedList 太容易出错了。该列表可以增长到几千个元素并且被大量使用,所以我很确定CopyOnWriteArrayList 会太慢。我将从它开始以避免过早的优化,但我敢打赌它不会很好地工作。

大多数访问将是单线程读取。所以我在问什么是正确的数据结构。


我虽然将synchronizedList 包装在提供同步迭代器的东西中会做,但它不会因为ConcurrentModificationException。关于并发行为,我显然需要后续读取和迭代器可以看到所有更改。

迭代器不必显示一致的快照,它可能会或可能不会通过set(int index, E element) 看到更新,因为此操作仅用于将项目替换为其更新版本(包含一些附加信息,这是无关紧要的对于迭代器的用户)。这些项目是完全不可变的。


我清楚地说明了为什么CopyOnWriteArrayList 不会这样做。 ConcurrentLinkedQueue 毫无疑问,因为它缺少索引访问。我只需要几个操作而不是完全成熟的ArrayList。因此,除非任何与 java 并发列表相关的问题this question 重复,否则这个问题不是。

【问题讨论】:

  • 你可能想试试 Clojure 的PersistentVector,它也是copy-on-write,但不是一个简单的整体数组;而是一个宽而浅的数组树。在我链接到的源代码的末尾,您会找到测试代码。
  • @skaffman 您能否在关闭之前阅读该问题?查看我的更新。
  • @MarkoTopolnik 这可行,但代码看起来很奇怪。它还增加了我不关心的操作所需的一些开销。顺便说一句,你能投票重新开放吗?恕我直言,这个模组的阅读速度有点太快了。
  • 那是他的粗心大意 :) 是的,Rich Hickey 有一种特殊的编码风格,但您可能仍想重用该代码,删除所有多余的内容。我认为 Rich 的方法非常好。
  • 更多关于用例和目标的信息会很有帮助。速度似乎是其中之一,数据完整性也是如此。

标签: java arraylist concurrency


【解决方案1】:

在您的情况下,您可以使用ReadWriteLock 访问支持的列表,这允许多个线程读取您的列表。只有当一个线程需要写访问权限时,所有读线程必须等待操作完成。 JavaDoc 清楚地说明了这一点:

ReadWriteLock 维护一对关联的锁,一个用于 只读操作和一个用于写入的操作。读锁可能被持有 同时由多个阅读器线程,只要没有 作家。写锁是独占的。

这是一个示例:

public class ConcurrentArrayList<T> {

    /** use this to lock for write operations like add/remove */
    private final Lock readLock;
    /** use this to lock for read operations like get/iterator/contains.. */
    private final Lock writeLock;
    /** the underlying list*/
    private final List<T> list = new ArrayList();

    {
        ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        readLock = rwLock.readLock();
        writeLock = rwLock.writeLock();
    }

    public void add(T e){
        writeLock.lock();
        try{
            list.add(e);
        }finally{
            writeLock.unlock();
        }
    }

    public void get(int index){
        readLock.lock();
        try{
            list.get(index);
        }finally{
            readLock.unlock();
        }
    }

    public Iterator<T> iterator(){
        readLock.lock();
        try {
            return new ArrayList<T>( list ).iterator();
                   //^ we iterate over an snapshot of our list 
        } finally{
            readLock.unlock();
        }
    }

【讨论】:

  • 没关系,但我不能使用ArrayList 的迭代器,因为它会抛出ConcurrentModificationException。我想,有一个简单的解决方法。
  • 如果您希望能够在迭代时添加元素,则无法避免“ConcurrentModificationException”。想象一下在迭代时您的下一个元素位于索引 5 处,但同时另一个线程在索引 1 处添加了一个元素,现在应该发生什么?唯一的出路是创建 List 的不可变副本以获得独立的迭代器或滥用 writeLock 来锁定迭代循环块。
  • "现在应该发生什么" - 这完全不可能发生,因为不会有这样的操作。但是添加或替换元素可以,然后我就不在乎了。获取旧值很好,因此获取新值。
  • 此解决方案可能不适合大型列表,因为您在持有锁的同时进行大量复制(在additerator 中)。 (您的迭代器实现还提供了必要的更强的一致性保证。)
猜你喜欢
  • 1970-01-01
  • 2012-10-23
  • 2013-04-09
  • 2020-01-26
  • 2015-01-02
  • 2011-08-16
  • 2021-10-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多