-1

CopyOnWriteArrayList 几乎具有我想要的行为,如果删除了不必要的副本,那将正是我想要的。特别是,对于在 ArrayList 末尾添加的内容,它的行为可能与 ArrayList 完全相同 - 即,没有理由每次都创建一个新副本,这是非常浪费的。它可以虚拟地限制 ArrayList 的结尾以捕获读者的快照,并在添加新项目后更新结尾。

这种增强似乎值得拥有,因为对于许多应用程序来说,最常见的添加类型是在 ArrayList 的末尾——这甚至是选择使用 ArrayList 开始的原因。

也不会有额外的开销,因为它只能在追加时不能复制,尽管它仍然必须检查是否需要重新调整大小,但无论如何 ArrayList 都必须这样做。

  1. 是否有任何替代实现或数据结构具有这种行为,而无需在末尾添加不必要的副本(即线程安全且经过优化以允许频繁读取,而写入只是在列表末尾添加)?

  2. 如何提交更改请求以请求更改 Java 规范以消除添加到 CopyOnWriteArrayList 末尾的副本(除非需要重新调整大小)?

我真的很想看到核心 Java 库改变了这一点,而不是维护和使用我自己的自定义代码。

4

4 回答 4

4

听起来您正在寻找 a BlockingDeque,尤其是 a ArrayBlockingQueue

您可能还需要一个ConcurrentLinkedQueue,它使用“无等待”算法(也称为非阻塞),因此在许多情况下可能更快。它只是 a Queue(不是 a Dequeue),因此您只能在集合的头部插入/删除,但这听起来可能对您的用例有好处。但是作为免等待算法的交换,它必须在内部使用链表而不是数组,这意味着更多的内存(当你弹出项目时包括更多的垃圾)和更差的内存局部性。无等待算法还依赖于比较和设置(CAS)循环,这意味着虽然它在“正常”情况下更快,但在高争用情况下它实际上可能更慢,因为每个线程需要多次尝试其 CAS它获胜并且能够前进。

我的猜测是,列表没有得到那么多喜爱的原因java.util.concurrent是,在大多数其他迭代的用例中,列表本质上是一种活泼的数据结构。例如,if (!list.isEmpty()) { return list.get(0); }除非它被一个块包围,否则它是活泼的synchronized,在这种情况下,您不需要固有的线程安全结构。你真正需要的是一个“列表类型”的界面,它只允许在末端进行操作——这正是它所需要QueueDeque

于 2014-01-08T22:36:02.587 回答
3

没有理由每次都制作一个新副本,这太浪费了。

这就是它的工作原理。它通过在比较和交换操作中用新数组替换以前的数组来工作。即使您所做的只是替换一个条目,您也始终拥有一个新数组,这是线程安全设计的一个关键部分。

线程安全且经过优化以允许频繁读取,而写入只是在列表末尾添加

这针对读取进行了高度优化,任何其他解决方案的写入速度都会更快,但读取速度会更慢,您必须确定您真正想要的解决方案。

您可以拥有一个自定义数据结构,这将是两全其美的,但它不再是 CopyOnWriteArrayList 和 ArrayDeque 提供的通用解决方案。

如何提交更改请求以请求更改 Java 规范以消除添加到 CopyOnWriteArrayList 末尾的副本(除非需要重新调整大小)?

您可以通过 bugs 数据库来做到这一点,但您提出的是数据结构工作方式的根本变化。我建议提出一个新的/不同的数据结构,它可以按照你想要的方式工作。同时,我建议您自己实现它作为一个工作示例,因为您会更快地想要您想要的。

我将从 AtomicReferenceArray 开始,因为它可用于执行您需要的低级操作。唯一的问题是它不可调整大小,因此您需要确定每个需要的最大大小。

于 2014-01-08T22:37:19.023 回答
3

要回答您的问题:

  1. 我不知道功能齐全的列表的替代实现。

  2. 如果您的想法确实可行,我可以考虑多种方法来进行:

    • 您可以通过 Java Bugs Database 提交“增强请求”(RFE)。但是,在这种情况下,我怀疑你会得到积极的回应。(当然,不是一个及时的!)

    • 您可以在 Guava 或 Apache Commons 问题跟踪器上创建 RFE 问题。这可能会更有成效,尽管这取决于说服他们......

    • 你可以向 OpenJDK 团队提交一个补丁来实现你的想法。我不能说结果可能是什么......

    • 您可以通过各自的问题跟踪器向 Guava 或 Apache Commons 提交补丁(如上)。这是最有可能成功的方法,尽管它仍然取决于让“他们”相信它在技术上是合理的,并且是“一件好事”。

    • 你可以把你提议的替代实现的代码放在 Github 上,看看会发生什么。


然而,所有这些都假设你的想法实际上会奏效。根据您提供的少量信息,我对此表示怀疑。我怀疑可能存在不完整封装、并发和/或未完全/正确实现 List 抽象的问题。

我建议您将代码放在 Github 上,以便其他人可以仔细查看。

于 2014-01-08T22:55:01.313 回答
0

CopyOnWriteArrayList 有一个性能缺陷,因为它会在写操作时创建列表的底层数组的副本。数组复制使写操作变慢。可能是,CopyOnWriteArrayList 有利于使用具有高读取率和低写入率的 List。

最终,我开始使用 java.util.concurrent.locks,ReadWriteLock 编写自己的实现。我只是通过维护对象级别的 ReadWriteLock 实例,并在读操作中获得读锁并在写操作中获得写锁来完成我的实现。代码看起来像这样。

public class ConcurrentList< T > implements List< T >
{
    private final ReadWriteLock  readWriteLock = new ReentrantReadWriteLock();
    private final List< T > list;

    public ConcurrentList( List<T> list )
    {
        this.list = list;
    }

    public boolean remove( Object o )
    {
        readWriteLock.writeLock().lock();
        boolean ret;
        try
        {
            ret = list.remove( o );
        }
        finally
        {
            readWriteLock.writeLock().unlock();
        }
        return ret;
    }

    public boolean add( T t )
    {
        readWriteLock.writeLock().lock();
        boolean ret;
        try
        {
            ret = list.add( t );
        }
        finally
        {
            readWriteLock.writeLock().unlock();
        }
        return ret;
    }

    public void clear()
    {
        readWriteLock.writeLock().lock();
        try
        {
            list.clear();
        }
        finally
        {
            readWriteLock.writeLock().unlock();
        }
    }


    public int size()
    {
        readWriteLock.readLock().lock();
        try
        {
            return list.size();
        }
        finally
        {
            readWriteLock.readLock().unlock();
        }
    }

    public boolean contains( Object o )
    {
        readWriteLock.readLock().lock();
        try
        {
            return list.contains( o );
        }
        finally
        {
            readWriteLock.readLock().unlock();
        }
    }

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

//etc
}

观察到的性能改进是显着的。

10 个线程 5000 次读取 + 5000 次写入(读写比率为 1:1)的总时间为

  • ArrayList - 16450 ns(不是线程安全的)
  • 并发列表 - 20999 ns
  • 矢量 -35696 ns
  • CopyOnWriteArrayList - 197032 ns

请点击此链接以获取有关用于获得上述结果的测试用例的更多信息

但是,为了避免在使用 Iterator 时出现 ConcurrentModificationException,我只是创建了当前 List 的副本并返回了它的迭代器。这意味着这个列表不会返回,并且可以修改原始列表的迭代器。好吧,对我来说,这暂时还可以。

public Iterator<T> iterator()
    {
        readWriteLock.readLock().lock();
        try
        {
            return new ArrayList<T>( list ).iterator();
        }
        finally
        {
            readWriteLock.readLock().unlock();
        }
    }

经过一番谷歌搜索后,我发现 CopyOnWriteArrayList 具有类似的实现,因为它不返回可以修改原始列表的迭代器。Javadoc 说,

返回的迭代器提供了构造迭代器时列表状态的快照。遍历迭代器时不需要同步。迭代器不支持 remove 方法。

于 2014-11-24T09:53:47.733 回答