ConcurrentHashMap 的弱一致性迭代器

Weakly consistent iterator by ConcurrentHashMap

Java 并发实践中提到:

The iterator returned by the ConcurrentHashMap are weakly consistent than fail-fast. A weakly consistent iterator can tolerate the concurrent modifications, traverses elements as they existed when the iterator was constructed, and may (but is not guaranteed to) reflect modifications to the collection after the construction of the iterator.

  1. 如何使迭代器在并发环境中具有弱一致性或故障安全性,因为 ConcurrentHashMap 的状态仍然会被修改。唯一的问题是它不会抛出 ConcurrentModificationException.
  2. 为什么在创建故障安全迭代器时集合返回故障快速迭代器有利于并发。

TL;DR:因为锁定。

如果你想要一个一致的迭代器,那么你必须将所有修改锁定到Map - 这在并发环境中是一个巨大的惩罚。

当然,如果您愿意,您可以手动执行此操作,但遍历 Map 并不是其目的,因此默认行为允许在迭代时进行并发写入。

相同的论点不适用于普通集合,它们只能(允许)由单个线程访问。 ArrayList 上的迭代应该是一致的,因此快速失败迭代器强制一致性。

您的具体情况的正确性

请记住,Fail Fast 迭代器迭代原始集合。

相反Fail Safe(a.k.a弱一致性)迭代器迭代原始集合的副本。因此,对原始集合的任何更改都不会被注意到,这就是它保证缺少 ConcurrentModificationExceptions 的方式。


回答您的问题:

  1. 使用故障安全迭代器有助于并发,因为您不必阻塞整个集合的读取线程。读取发生时,可以在下面修改集合。缺点是读取线程会将集合的状态视为创建迭代器时拍摄的快照。
  2. 如果上述限制不适用于您的特定用例(您的读者应该始终看到相同的集合状态),您必须使用 Fail Fast 迭代器并保留对集合的并发访问控制得更严格。

如您所见,这是用例的正确性和速度之间的权衡。

ConcurrentHashMap

ConcurrentHashMap (CHM) 利用多种技巧来增加访问的并发性。

  • 首先CHM实际上是多个地图的分组;每个 MapEntry 都存储在 中的一个中,每个段本身都是一个可以同时读取的哈希表(read 方法不会阻塞)。
  • 段数是3参数构造函数中的最后一个参数,称为concurrencyLevel(默认16 ).段的数量决定了整个数据中并发编写器的数量。额外的内部散列算法确保条目在段之间的平等分布。
  • 每个 HashMapEntry 的值都是 volatile 从而确保竞争修改和后续读取的细粒度一致性;每次读取都反映了最近完成的更新
  • 迭代器和枚举是故障安全 - 反映自创建iterator/enumeration以来某个时刻的状态;这允许以降低一致性为代价同时读取和修改。

首先,并发集合的迭代器不是 fail-safe,因为它们没有可以通过某种紧急程序以某种方式处理的故障模式。他们根本不会失败。

由于性能原因,非并发集合的迭代器是快速失败的,它们的设计方式不允许修改它们迭代的集合的内部结构。例如。哈希图的迭代器不知道如何在调整哈希图大小时发生的重组后继续迭代。

这意味着它们不仅会因为其他线程访问它们而失败,如果当前线程执行使迭代器假设无效的修改,它们也会失败。

这些集合不会忽略那些麻烦的修改和 return 不可预测和损坏的结果,而是尝试跟踪修改并在迭代期间抛出异常以通知程序员出了点问题。这叫做fail-fast.

那些快速失败机制也不是线程安全的。这意味着如果非法修改不是从当前线程发生,而是从不同的线程发生,则不能保证再检测到它们。在那种情况下,它只能被认为是一种尽力而为的故障检测机制。

另一方面,并​​发集合的设计方式必须能够同时处理多个写入和读取,并且底层结构不断变化。

所以迭代器不能总是假定底层结构在迭代期间永远不会被修改。

相反,它们旨在提供较弱的保证,例如迭代过时的数据,或者也可能显示在创建迭代器后发生的一些但不是全部更新。这也意味着当它们在单个线程中的迭代期间被修改时,它们可能 return 过时数据,这对于程序员来说可能有点违反直觉,因为人们通常希望在单个线程中立即看到修改。

示例:

HashMap:尽力而为的快速失败迭代器。

  • 迭代器支持移除
  • 来自同一线程的结构 修改,例如在迭代期间 clear()ing Map:保证在下一个迭代器步骤
  • 结构迭代期间来自不同线程的修改:迭代器通常会抛出异常,但也可能导致不一致、不可预测的行为

CopyOnWriteArrayList:快照迭代器

  • 迭代器不支持移除
  • 迭代器显示在创建时冻结的项目的视图
  • 集合可以在迭代期间被包括当前线程在内的任何线程修改而不会导致异常,但它对迭代器访问的项目没有影响
  • clear()编辑列表不会停止迭代
  • 迭代器从不抛出 CME

ConcurrentSkipListMap: 弱一致性迭代器

  • 迭代器支持删除,但可能会导致令人惊讶的行为,因为它仅基于映射键,而不是当前值
  • 迭代器可能会看到自创建以来发生的更新,但不保证一定会。这意味着,例如 clear()ing Map 可能会或可能不会停止迭代,删除条目可能会或可能不会阻止它们在剩余的迭代中出现
  • 迭代器从不抛出 CME