ConcurrentHashMap是完全安全的吗?

这是 JavaDoc 关于 的一段话。它说检索操作通常不会阻塞,因此可能与更新操作重叠。这是否意味着该方法不是线程安全的?ConcurrentHashMapget()

“但是,即使所有操作都是线程安全的,检索操作也不需要锁定,并且不支持以阻止所有访问的方式锁定整个表。此类在依赖于其线程安全但不依赖于其同步详细信息的程序中与 Hashtable 完全互操作。

检索操作(包括get)一般不阻塞,因此可能与更新操作(包括放置和删除)重叠。检索反映了最近完成的更新操作在其启动时保持的结果。


答案 1

该方法是线程安全的,其他用户为您提供了有关此特定问题的有用答案。get()

但是,尽管 是 的线程安全直接替换,但重要的是要认识到,如果您正在执行多个操作,则可能必须对代码进行重大更改。例如,采用以下代码:ConcurrentHashMapHashMap

if (!map.containsKey(key)) 
   return map.put(key, value);
else
   return map.get(key);

在多线程环境中,这是一个争用条件。您必须使用 ConcurrentHashMap.putIfAbsent(K 键,V 值)并注意返回值,这会告诉您看跌操作是否成功。有关更多详细信息,请阅读文档。


回答要求澄清为什么这是竞争条件的评论。

想象一下,有两个线程 ,它们将分别在映射中放置两个不同的值,并且具有相同的键。该键最初不存在于地图中。它们以这种方式交错:ABv1v2

  • 线程调用并发现密钥不存在,但会立即挂起。AcontainsKey
  • 线程调用并发现密钥不存在,并且有时间插入其值。BcontainsKeyv2
  • 线程恢复并插入 ,“和平地”覆盖(因为线程安全)线程 插入的值。Av1putB

现在线程“认为”它已经成功地插入了自己的值,但是地图包含。这实际上是一场灾难,因为线程可能会调用并“认为”映射的使用者(例如其他线程)可以访问该对象,并将看到可能重要的更新(“例如:此访问者IP地址正在尝试执行DOS,从现在开始拒绝所有请求”)。相反,该对象将很快被垃圾回收并丢失。Bv2v1Bv2.updateSomething()


答案 2

它是线程安全的。但是,线程安全的方式可能不是您所期望的。您可以从以下位置看到一些“提示”:

此类可与依赖于其线程安全性但不依赖于其同步详细信息的程序完全互操作Hashtable

要以更完整的画面了解整个故事,您需要了解界面。ConcurrentMap

原始文件提供了一些非常基本的读取/更新方法。甚至我能够使 ;有很多情况下,如果不考虑我的同步机制,人们就无法使用我的地图。这是一个典型的例子:MapMap

if (!threadSafeMap.containsKey(key)) {
   threadSafeMap.put(key, value);
}

这段代码不是线程安全的,即使映射本身是线程安全的。同时调用的两个线程可能会认为没有这样的密钥,因此它们都插入到 .containsKey()Map

为了解决这个问题,我们需要显式地进行额外的同步。假设我的Map的线程安全性是通过同步关键字实现的,您将需要这样做:

synchronized(threadSafeMap) {
    if (!threadSafeMap.containsKey(key)) {
       threadSafeMap.put(key, value);
    }
}

这种额外的代码需要您了解映射的“同步详细信息”。在上面的例子中,我们需要知道同步是通过“同步”实现的。

ConcurrentMap接口更进一步。它定义了一些常见的“复杂”操作,这些操作涉及对地图的多次访问。例如,上面的示例公开为 。通过这些“复杂”操作,(在大多数情况下)的用户无需将操作与对地图的多个访问权限同步。因此,Map的实现可以执行更复杂的同步机制以获得更好的性能。 就是一个很好的例子。实际上,线程安全是通过为映射的不同分区保留单独的锁来维护的。它是线程安全的,因为对映射的并发访问不会破坏内部数据结构,也不会导致任何意外丢失更新等。putIfAbsent()ConcurrentMapConcurrentHashhMap

考虑到上述所有情况,Javadoc的含义将更加清晰:

“检索操作(包括 get)通常不会阻塞”,因为没有使用“synchronized”作为其线程安全。逻辑本身负责线程安全;如果你在Javadoc中进一步看:ConcurrentHashMapget

该表在内部分区,以尝试允许指定数量的并发更新而不会发生争用

检索不仅是非阻塞的,甚至更新也可以同时发生。但是,非阻塞/并发更新并不意味着它是线程 UNsafe。它只是意味着它正在使用一些方法,而不是简单的“同步”线程安全。

但是,由于内部同步机制未公开,因此,如果要执行 除 提供的操作之外的一些复杂操作,则可能需要考虑更改逻辑,或者考虑不使用 .例如:ConcurrentMapConcurrentHashMap

// only remove if both key1 and key2 exists
if (map.containsKey(key1) && map.containsKey(key2)) {
    map.remove(key1);
    map.remove(key2);
}