132

我的线程池有固定数量的线程。这些线程需要阅读经常从共享列表中删除。

因此,在中的哪个数据结构(最好是List,必须是无监视器的)java.util.concurrent(java.utilconcurrent)这种情况下包装最好吗?

5
  • 5
    这取决于你想用这个收藏做什么。请参阅我的博客帖子(虽然它是关于.Net的,但概念是一样的)。使用列表. 评论 2011年11月20日19:01
  • 1
    现在,我正在使用CopyOnWriteArrayList(写入阵列列表时复制),但是并发修改异常偶尔仍会抛出异常。 评论 2011年11月20日19:22
  • 2
    请提供更多信息,说明你在收集什么,这样人们可以更好地回答,否则只是猜测。 评论 2011年11月20日19:34
  • 这个并发修改异常可能不是来自同步问题;例如,它也会出现在集合上的for循环中,您试图从集合中删除元素。
    – 总计2
    评论 2011年11月20日19:55
  • 1
    我知道这不是套餐的一部分,但有人试过吗矢量? 评论 2019年3月18日11:23

6个答案6

重置为默认值
117

最好是列表

这个只有 列表在中执行java.util.concurrent(java.utilconcurrent)CopyOnWriteArrayList(写入阵列列表时复制)正如Travis Webb提到的,还有一个同步列表选项。

也就是说,你确定你需要它成为列表? 有更多的并发选项排队s和地图s(你可以设置s来自地图s) 这些结构往往对您想用共享数据结构做的许多类型的事情最有意义。

对于队列,您有大量选项,而哪种选项最合适取决于您需要如何使用它:

4
  • 21
    CopyOnWriteArrayList(写入阵列列表时复制)缺点是写操作非常昂贵(但读操作很便宜)。如果要进行大量写操作,最好使用同步列表或队列。 评论 2011年11月21日8:00
  • 为了补充这个答案,一般来说,只有当存在更改数据或添加/删除数据的操作时,您才会希望数据结构是线程安全的。因此,除非您需要通过一些“set”方法调用更改“List”中的数据,否则唯一的其他操作即添加/删除操作很容易由Queue数据结构处理(这也是一种最佳实践,当然,除非需要异常)。 评论 2021年4月7日18:27
  • 关于矢量? 评论 5月22日20:20
  • 矢量是一个过时的API,不应使用。集合.synchronizedList是同步列表的更好替代方案。 评论 5月24日16:51
105

任何Java集合都可以成为线程安全的,如下所示:

List newList=Collections.synchronizedList(oldList);

或者创建一个全新的线程安全列表:

List newList=Collections.synchronizedList(new ArrayList());

http://download.oracle.com/javase/6/docs/api/java/util/Collections.html#synchronizedList(java.util.List)

8
  • 11
    因此,在java.util.concurrent中找不到列表实现--嗯,有一个并发哈希映射即使有一个集合.synchronizedMap方法。
    – 艾奥贝
    评论 2011年11月20日19:06
  • 12
    阅读上的Javadocs并发哈希映射。同步实现的细节有所不同。使用已同步中的方法收藏基本上只是将类包装在Java监视器中。并发哈希映射使用更聪明的并发功能。 评论 2011年11月20日19:08
  • 4
    是的。不过,这让你的最后一句话有点无效。
    – 艾奥贝
    评论 2011年11月20日19:08
  • 11
    只需补充一下,newList上的迭代不是线程安全的!!
    – 蓝潜蝇
    评论 2017年3月12日9:43
  • 2
    @如果您“在遍历返回列表时手动同步该列表”(java文档),则bluelurker线程是安全的。synchronized(list){迭代器i=list.Iterator();//必须在synchronize块中,而(i.hasNext())是foo(i.next();} 评论 2018年1月10日21:18
10

如果列表的大小固定,则可以使用原子引用数组。这将允许您对插槽执行索引更新。如果需要,可以编写List视图。

9

并发链接队列使用无锁队列(基于较新的CAS指令).

8
  • 9
    …它没有实现列表接口。
    – 艾奥贝
    评论 2011年11月20日19:04
  • 1
    eSniff,您将如何实施List.set(int索引,Object元素)使用ConcurrentLinkedQueue? 评论 2011年11月20日19:17
  • 4
    大多数列表-使用排队(例如,在特定索引处添加/设置),或者可以实现,但效率低下(从索引中获取)。所以我认为你不可能真的把它包装起来。也就是说,我认为排队很好,因为OP没有真正解释他们为什么需要列表. 评论 2011年11月20日19:20
  • 1
    @科林,这就是我想要的答案。CLQ不能封装在List中有很好的理由。虽然我同意,但不能排除Queue接口。 评论 2011年11月20日19:24
  • 2
    ❗️ 值得注意的是:注意,与大多数集合不同,size方法不是一个恒定时间操作。由于这些队列的异步性质,确定当前元素数需要遍历元素 评论 2018年1月29日3:13
6

你可能想看看并发双链接列表由Doug Lea根据Paul Martin的“实用无锁双链接列表”编写。它没有实现java.util。列表接口,但提供了在列表中使用的大多数方法。

根据javadoc:

Deque的并发链接列表实现(双端队列)。并发插入、删除和访问操作可以跨多个线程安全执行。迭代器是弱一致的,返回反映状态的元素迭代器创建时或创建后的某个时间点的deque。他们引发ConcurrentModificationException,并且可以与其他操作同时进行。

0

如果设置足够,并发跳过列表集可能会用到。(其实施基于并发跳过列表映射它实现了跳跃表.)

包含、添加和删除操作的预期平均时间成本为log(n);size方法不是一个固定时间的操作。

2
  • 但它不是列表。不能添加具有相同hashcod倍数的对象。 评论 2020年11月29日21:31
  • 1
    这就是为什么我写道:“如果设置足够……”
    – 安雷
    评论 2020年11月30日16:30

不是你想要的答案吗?浏览标记的其他问题问你自己的问题.