在Java中似乎有很多不同的实现和方法来生成线程安全的集合。 一些例子包括
2)Collections.synchronizedSet(Set set)
4)Collections.newSetFromMap(new ConcurrentHashMap())
5)以类似于(4)
的方式生成的其他集合这些示例来自Concurrency Pattern: Concurrent Set implementations in Java 6
有人可以简单解释这些例子和其他例子的差异,优点和缺点吗?我无法理解并保持Java Std Docs中的所有内容。
答案 0 :(得分:187)
1)CopyOnWriteArraySet
是一个非常简单的实现 - 它基本上有一个数组中的元素列表,当更改列表时,它会复制数组。此时运行的迭代和其他访问继续使用旧数组,避免了读取器和写入器之间同步的必要性(尽管写入本身需要同步)。通常快速设置的操作(尤其是contains()
)在这里非常慢,因为将在线性时间内搜索数组。
仅将此用于非常小的集合,这些集合将经常被读取(迭代)并且很少被更改。 (Swings听众集就是一个例子,但这些并不是真正的集合,而且应该只在EDT中使用。)
2)Collections.synchronizedSet
将简单地围绕原始集的每个方法包装一个synchronized块。您不应直接访问原始集。这意味着集合中没有两个方法可以同时执行(一个将阻塞直到另一个完成) - 这是线程安全的,但如果多个线程真正使用该集合,则不会有并发性。如果使用迭代器,在修改迭代器调用之间的集合时,通常仍需要在外部进行同步以避免ConcurrentModificationExceptions。性能将类似于原始集的性能(但有一些同步开销,如果同时使用则会阻塞)。
如果您只有较低的并发性,并希望确保所有更改立即对其他线程可见,请使用此方法。
3)ConcurrentSkipListSet
是并发SortedSet
实现,其中大多数基本操作都在O(log n)中。它允许并发添加/删除和读取/迭代,其中迭代可能会或可能不会告诉自迭代器创建以来的更改。批量操作只是多次单个调用,而不是原子操作 - 其他线程可能只观察其中的一些。
显然,只有在元素上有一些总订单时才能使用它。 对于高并发情况,这看起来是理想的候选者,对于不太大的集合(因为O(log n))。
4)对于ConcurrentHashMap
(以及从中派生的集合):在O(1)中,大多数基本选项(平均来说,如果你有一个好的和快hashCode()
)(但是可能会退化为O(n)),就像HashMap / HashSet一样。写入的并发性有限(表是分区的,写访问将在所需的分区上同步),而读访问与自身和写线程完全并发(但可能还没有看到当前更改的结果)书面)。迭代器可能会也可能看不到自创建以来的更改,并且批量操作不是原子操作。
调整大小很慢(对于HashMap / HashSet),因此尝试通过估计创建时所需的大小来避免这种情况(并使用大约1/3,因为它在3/4完整时调整大小)。
当你有大型集合,一个好的(和快速)散列函数时使用它,并且可以在创建映射之前估计集合大小和所需的并发性。
5)在这里可以使用其他并发地图实现吗?
答案 1 :(得分:19)
可以使用contains()
将HashSet
的{{1}}性能与CopyOnWriteArraySet
的并发相关属性结合起来,并在每次修改时替换整个集合。
实施草图:
AtomicReference<Set>
答案 2 :(得分:10)
如果Javadocs没有帮助,你可能应该找一本书或文章来阅读有关数据结构的内容。一目了然:
答案 3 :(得分:0)
另一种扭曲是weak references的线程安全集合。
在pub-sub场景中,这样的集合对于跟踪订户很方便。当订户在其他地方超出范围,并因此成为垃圾收集的候选者时,不需要为订户优雅地取消订阅所困扰。弱引用使订户可以完成向垃圾回收候选者的过渡。当最终收集到垃圾时,集合中的条目将被删除。
虽然捆绑类没有直接提供这样的集合,但是您可以通过几次调用来创建一个集合。
首先,我们通过利用WeakHashMap
类来创建Set
弱引用。这显示在Collections.newSetFromMap
的课程文档中。
Set< YourClassGoesHere > weakHashSet =
Collections
.newSetFromMap(
new WeakHashMap< YourClassGoesHere , Boolean >()
)
;
地图的值 Boolean
与此处无关,因为地图的 Key 构成了我们的Set
。
在诸如pub-sub之类的方案中,如果订阅者和发布者在不同的线程上运行(我们很可能是这种情况),我们需要线程安全。
将其包装为同步集,使该集成为线程安全的,进一步走了一步。馈入对Collections.synchronizedSet
的呼叫。
this.subscribers =
Collections.synchronizedSet(
Collections.newSetFromMap(
new WeakHashMap <>() // Parameterized types `< YourClassGoesHere , Boolean >` are inferred, no need to specify.
)
);
现在,我们可以从生成的Set
中添加和删除订阅者。执行垃圾收集后,所有“消失的”订户最终都会被自动删除。执行时间取决于您的JVM的垃圾收集器实现,还取决于当前的运行时情况。有关底层WeakHashMap
何时以及如何清除过期条目的讨论和示例,请参阅问题*Is WeakHashMap ever-growing, or does it clear out the garbage keys?
*。