Java的JDK中是否有并发List?

Ali*_*aka 248 java concurrency list

如何创建并发List实例,我可以通过索引访问元素?JDK是否有我可以使用的任何类或工厂方法?

小智 159

java.util.concurrent中有一个并发列表实现.特别是CopyOnWriteArrayList.

  • 请注意,它会复制每个插入的整个列表,因此通常效率低下. (81认同)
  • @dfrankow但是,如果你的迭代次数远远超过你的更新,它可以更有效*. (18认同)
  • 这个答案——已被接受并获得 200 多个赞成票——具有误导性,因为它是解决 ArrayList 并发问题的正确方法。正如前面的评论所描述的,如果使用情况主要是写入,而读取很少,那么 CopyOnWriteArrayList 不太适合。@Oliv 发布的答案更加完整且信息丰富。 (2认同)

Mat*_*ell 146

如果您不关心具有基于索引的访问权限并且只想要List的插入顺序保留特性,则可以考虑使用java.util.concurrent.ConcurrentLinkedQueue.由于它实现了Iterable,一旦你完成了所有项的添加,你就可以使用增强的for语法循环遍历内容:

Queue<String> globalQueue = new ConcurrentLinkedQueue<String>();

//Multiple threads can safely call globalQueue.add()...

for (String href : globalQueue) {
    //do something with href
}
Run Code Online (Sandbox Code Playgroud)

  • 我认为简化的for语句(`:`)叫做foreach:http://docs.oracle.com/javase/1.5.0/docs/guide/language/foreach.html (3认同)
  • @ AlikElzin-kilaka Nitpicking,但根据[JLS版本8](https://docs.oracle.com/javase/specs/jls/se8/html/jls-14.html#jls-14.14.2),它是称为"增强陈述".[java教程](https://docs.oracle.com/javase/tutorial/java/nutsandbolts/for.html)中也是如此. (3认同)
  • @Roland 间接的。我相信他们将“for each loop”重命名为“enhanced for”,以消除 Stream.forEach 和现在所谓的增强 for 之间的混淆。 (3认同)
  • @ AlikElzin-kilaka你是对的.我认为这个名字一直困扰着我,因为实际的语法不包含单词"each",但我会更新答案以使用官方名称.:) (2认同)

Yan*_*hon 121

如果你需要的只是简单的调用同步,你可以很好地使用Collections.synchronizedList(List):

 List<Object> objList = Collections.synchronizedList(new ArrayList<Object>());
Run Code Online (Sandbox Code Playgroud)

  • `synchronizedList`的结果是"同步"但不是"并发".许多List操作(基于索引)的一个基本问题本身不是原子的,需要成为更大的互斥构造的一部分. (63认同)
  • IMO,asing一个`Vector`比`Collections.synchronizedList(new ArrayList <Object>())`更直接. (6认同)
  • synchronizedList的结果是"同步"但不是"并发". (6认同)

Joa*_*uer 43

因为获取位置并从给定位置获取元素的行为自然需要一些锁定(您不能让列表在这两个操作之间进行结构更改).

并发集合的想法是每个操作本身都是原子操作,可以在没有显式锁定/同步的情况下完成.

因此,在预期并发访问的情况下,将元素n从给定的位置List作为原子操作获得并不太有意义.

  • 约阿希姆,我觉得你的头上钉了一针.例如,将只读列表作为并发列表.从列表中获取位置N的元素不仅有意义,而且它是问题的概括.因此,不可变列表(小写L)将是一个很好的例子,但它不是List(大写L).CopyOnWriteArrayList是并发的,但很多人不喜欢这种性能.沿着绳索(绳索)的解决方案可能是一个很好的赢家. (5认同)
  • 非常好的一点。但是OP将要使用的列表可能有非常具体的用法。例如,它可能会在并发环境中填充,然后“锁定”(无论它意味着什么),然后通过索引安全地访问。因此,在填充这样一个 List 的第一阶段仍然需要线程安全的实现。不幸的是,OP 并没有具体说明他正在寻找的列表将如何使用。 (2认同)

Oli*_*liv 27

您有以下选择:

  • Collections.synchronizedList():您可以包装任何List实现(ArrayListLinkedList或第 3 方列表)。对每种方法(读取和写入)的访问都将使用synchronized. 使用iterator()或增强for循环时,必须手动同步整个迭代。在迭代时,其他线程甚至无法读取。您也可以为每个hasNextnext呼叫分别同步,但这ConcurrentModificationException是可能的。

  • CopyOnWriteArrayList: 修改成本高,但无需等待阅读。迭代器从不 throw ConcurrentModificationException,它们在迭代器创建时返回列表的快照,即使列表在迭代时被另一个线程修改。对不经常更新的列表很有用。批量操作喜欢addAll更新 - 内部数组被复制的次数更少。

  • Vector: 非常像synchronizedList(new ArrayList<>()),但迭代也是同步的。但是,ConcurrentModificationException如果向量在迭代时被另一个线程修改,则迭代器可能会抛出异常。

其他选项:

  • Queue或者Deque如果您只在列表的末尾添加/删除或迭代它,则可能是另一种选择。Queue只允许在一端添加并从另一端Deque删除,允许在两端添加和删除。没有索引访问。有多种实现具有比任何实现都更好的并发属性List。查看Queue javadoc中的“所有已知实现类”,java.util.concurrent包中的那些实现是并发的。您还可以查看JCTools,它包含专用于单个消费者或单个生产者的更快的队列实现。
  • Collections.unmodifiableList(): 无等待,线程安全,但不可修改
  • List.of& List.copyOf:Java 9 及更高版本中的另一个不可修改的列表


vaq*_*han 9

在此输入图像描述

CopyOnWriteArrayList 是 ArrayList 的线程安全变体,其中所有可变操作(添加、设置等)都是通过制作底层数组的新副本来实现的。

CopyOnWriteArrayList 是同步 List 的并发替代方案,实现 List 接口及其 java.util.concurrent 包的一部分及其线程安全集合。

public class CopyOnWriteArrayList<E>
    implements List<E>, RandomAccess, Cloneable, java.io.Serializable
Run Code Online (Sandbox Code Playgroud)

CopyOnWriteArrayList 是故障安全的,并且在使用 ArrayList 的单独副本进行迭代期间修改基础 CopyOnWriteArrayList 时不会引发 ConcurrentModificationException。

这通常成本太高,因为复制数组涉及每次更新操作,都会创建克隆副本。CopyOnWriteArrayList仅是频繁读取操作的最佳选择。

/**
         * Returns a shallow copy of this list.  (The elements themselves
         * are not copied.)
         *
         * @return a clone of this list
         */
        public Object clone() {
            try {
                @SuppressWarnings("unchecked")
                CopyOnWriteArrayList<E> clone =
                    (CopyOnWriteArrayList<E>) super.clone();
                clone.resetLock();
                return clone;
            } catch (CloneNotSupportedException e) {
                // this shouldn't happen, since we are Cloneable
                throw new InternalError();
            }
        }
Run Code Online (Sandbox Code Playgroud)