关于并发哈希表的内部工作方式

4
我正在阅读关于ConcurrentHashMap的相关教程(参见此处),有一些疑问。
  1. 在这篇文章中提到,ConcurrentHashMap 允许多个读取器并发读取而不会阻塞。这是通过根据并发级别将 Map 分成不同的部分,并仅在更新期间锁定 Map 的一部分来实现的。默认并发级别为 16,因此 Map 被分成 16 部分,每个部分都受不同的锁控制。这意味着,16 个线程可以同时操作 Map,直到它们正在操作 Map 的不同部分。尽管如此,这也有一个警告:由于更新操作(如 put()remove()putAll()clear())未进行同步,因此并发检索可能无法反映 Map 上最新的更改

  2. 文章中还提到了另一个重要点:另一个需要记住的重要点是,在 CHM 上迭代时,keySet 返回的迭代器是弱一致的,它们仅反映某个特定时间点上的 ConcurrentHashMap 状态,并可能不反映任何最近的更改

我没有理解加粗的重点,你能提供更多信息或者用一个简单的程序演示一下吗?


这是对你第一个问题的回答:https://dev59.com/BGUp5IYBdhLWcg3w665C#14947818 - Lee Meador
2个回答

2
  1. Since update operations like put(), remove(), putAll() or clear() is not synchronized, concurrent retrieval may not reflect most recent change on Map

    As I understand it, this means that a modification of the map in one thread may not necessarily be seen by a retrieval happening at the same time in another thread. Consider the following example:

                      Thread 1 starts              Thread 1's call to get("a")
                     a call to get("a")             completes, returning null
                             |                                 |
    Thread 1        ---------+---------------------------------+-------
                                 time ----->
    Thread 2        -----+---------------------------+-----------------
                         |                           |
                 Thread 2 starts a            Thread 2's call to
                call to put("a", 1)          put("a", 1) completes
    

    Even though Thread 2 put a value in the map Thread 1's get completed execution, Thread 1 did not "see" the map modification, and returned null.

  2. Another important point to remember is iteration over CHM, Iterator returned by keySet of ConcurrentHashMap are weekly consistent and they only reflect state of ConcurrentHashMap and certain point and may not reflect any recent change.

    This is a similar situation. If Thread 1 obtains an Iterator from a ConcurrentHashMap's keySet, and later Thread 2 puts a new entry in the map, Thread 1's Iterator is not guaranteed to see that entry. (It may or it may not.)


0

这里的真正问题是,当多个线程操作数据结构时,这些线程不一定会同步进行。

一个线程正在为用户1读取数据。另一个线程正在为用户2写入数据。两个线程都无法预测对方线程在各自进程中的位置。此外,我们无法为用户预测这两个进程完成的任何排序方式。如果写操作先更新了数据,则即使用户1稍早请求了读取操作,读取操作也将显示更新后的状态。

在迭代时进行读取或修改的工作方式与上述相同,但需要额外考虑到移动到下一个元素(在迭代时)的过程实际上成为Map状态的“读取”操作,而不是其中任何特定数据的内容。

因此,当您允许并发使用这些数据结构时,您最终会得到一个“足够接近”的时间测试结果。(这很像数据库的考虑方式,只不过我们习惯于这样考虑数据库,并且时间范围有几个数量级的差异。)

注意:关于@Matts在另一个答案中展示的精美时间轴的评论...

时间轴显示了两个线程以及每个线程的开始和结束。两个线程的开始可以按任意顺序发生(a,b)或(b,a)。结束也可以按任意顺序发生,因为您无法确定操作需要多长时间。这给出了两个线程可以启动和完成的4种方式。(a先启动并先结束,a先启动并b先结束,b先启动并a先结束,b先启动并b先结束)现在...想象一下,有20个线程都在响应于20个终端用户提交的请求做同样的事情。它可以有多少种可能的工作方式。

你能否展示一个小程序来更加清晰地理解? - user2094103
不完全是这样。这个问题只会偶尔出现,而且只有在来自不同线程的事情大约同时发生时才会出现。你无法编写程序来解决这个问题,因为它将取决于处理器的速度和JVM运行代码时可用的CPU数量。 - Lee Meador
1
我应该指出,过去我曾尝试编写低级单元测试来引发这种情况。在那种情况下,线程之间的不一致性是一个错误。我从未找到一个好的方法来解决它。你有时可以在特定的处理器配置下使其发生一小部分时间。然后测试包括重复测试X次并期望它失败Y%的时间。一旦测试机器被换成另一台...它就不会再发生了。但即使在那个时候,拥有一个更像政治民意调查的测试也不是很令人满意。 - Lee Meador

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接