Java ConcurrentHashMap完全安全吗?

Java ConcurrentHashMap完全安全吗?,java,multithreading,synchronized,java.util.concurrent,Java,Multithreading,Synchronized,Java.util.concurrent,这是一篇来自JavaDoc的关于ConcurrentHashMap的文章。它说检索操作通常不会阻塞,因此可能与更新操作重叠。这是否意味着get()方法不是线程安全的 但是,即使所有操作都是线程安全的,检索 操作不需要锁定,也不支持 以阻止所有访问的方式锁定整个表。此类 在依赖其 线程安全性,但不取决于其同步细节 检索操作(包括get)通常不会阻塞,因此 与更新操作重叠(包括放置和删除)。检索 反映最近完成的更新操作的结果 在他们开始的时候坚持。” ConcurrentHashmap.get()是

这是一篇来自JavaDoc的关于
ConcurrentHashMap
的文章。它说检索操作通常不会阻塞,因此可能与更新操作重叠。这是否意味着
get()
方法不是线程安全的

但是,即使所有操作都是线程安全的,检索 操作不需要锁定,也不支持 以阻止所有访问的方式锁定整个表。此类 在依赖其 线程安全性,但不取决于其同步细节

检索操作(包括get)通常不会阻塞,因此 与更新操作重叠(包括放置和删除)。检索 反映最近完成的更新操作的结果 在他们开始的时候坚持。”


ConcurrentHashmap.get()
是线程安全的,即

  • 它不会引发任何异常,包括
    ConcurrentModificationException
  • 它将返回一个在过去某个时间(最近)为真的结果。这意味着要获取的两个背靠背调用可以返回不同的结果。当然,这也适用于任何其他
    Map

这只是意味着,当一个线程正在更新,而另一个线程正在读取时,不能保证首先调用ConcurrentHashMap方法的线程将首先执行操作

想一想关于物品的更新,告诉Bob在哪里。如果一个线程询问Bob在哪里,而另一个线程更新说他是“内部”的,那么您无法预测读者线程将获得Bob的状态是“内部”还是“外部”。即使更新线程首先调用该方法,读取器线程也可能获得“外部”状态

这些线程不会相互造成问题。代码是线程安全的

一个线程不会进入无限循环,也不会开始生成wierd NullPointerExceptions,也不会以一半旧状态和一半新状态获取“itside”。

方法是线程安全的,
get()

然而,尽管
ConcurrentHashMap
是线程安全的替代品,但重要的是要认识到,如果要执行多个操作,可能必须显著更改代码。例如,以以下代码为例:

if (!map.containsKey(key)) 
   return map.put(key, value);
else
   return map.get(key);
在多线程环境中,这是一种竞争条件。您必须使用,并注意返回值,它会告诉您put操作是否成功。阅读文档了解更多详细信息


回答一个要求澄清为什么这是一个种族条件的评论

假设有两个线程
A
B
将在映射中放置两个不同的值,
v1
v2
分别具有相同的键。该键最初不在地图中。它们以这种方式交错:

  • 线程
    A
    调用
    containsKey
    并发现密钥不存在,但立即挂起
  • 线程
    B
    调用
    containsKey
    ,发现密钥不存在,并有时间插入其值
    v2
  • 线程
    A
    恢复并插入
    v1
    ,“和平地”覆盖(因为
    put
    是线程安全的)线程
    B
    插入的值

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

HashMap
根据
hashCode
分为两部分
ConcurrentHashMap
使用此事实。它的同步机制基于阻塞桶而不是整个
Map
。这样,几个线程可以同时写入几个不同的bucket(一个线程一次可以写入一个bucket)

ConcurrentHashMap
读取几乎不使用同步。同步是在获取键的值时看到
null
值时使用的。由于<代码> CONTURNESHASMAP 不能存储<代码> null <代码>作为值(是的,除了关键字之外,值也不能是代码> null < /代码> s),这意味着读取另一个线程时初始化映射项(键值对)时发生的“<代码> null >代码>:当键被分配,但值尚未被分配时,并且它仍然保持默认的空值。
在这种情况下,读取线程将需要等待,直到条目被完全写入


因此,
read()
的结果将基于地图的当前状态。如果你读了更新中的关键字的值,那么由于写过程还没有完成,你很有可能得到旧的值。然而,线程安全的方式可能不是您所期望的。您可以从中看到一些“提示”:

此类可在以下程序中与哈希表
完全互操作: 依赖于它的线程安全性,但不依赖于它的同步细节

要更全面地了解整个情况,您需要了解
ConcurrentMap
界面

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

if (!threadSafeMap.containsKey(key)) {
   threadSafeMap.put(key, value);
}
这段代码不是thread-s
synchronized(threadSafeMap) {
    if (!threadSafeMap.containsKey(key)) {
       threadSafeMap.put(key, value);
    }
}
// only remove if both key1 and key2 exists
if (map.containsKey(key1) && map.containsKey(key2)) {
    map.remove(key1);
    map.remove(key2);
}