这是JavaDoc关于ConcurrentHashMap
的一段话。它说检索操作通常不会阻塞,因此可能与更新操作重叠。这是否意味着get()
方法不是线程安全的?
“但是,即使所有操作都是线程安全的,检索 操作不需要锁定,也没有任何支持 以阻止所有访问的方式锁定整个表。这个班 在依赖于Hashtable的程序中与Hashtable完全互操作 线程安全,但没有关于其同步细节。
检索操作(包括get)一般不会阻塞,所以可能 与更新操作重叠(包括put和remove)。检索 反映最近完成的更新操作的结果 坚持他们的发作。“
答案 0 :(得分:47)
get()
方法是线程安全的,其他用户为您提供了有关此特定问题的有用答案。
但是,尽管ConcurrentHashMap
是HashMap
的线程安全的 drop-in 替代品,但重要的是要意识到如果您正在进行多项操作,则可能需要显着改变您的代码。例如,请使用以下代码:
if (!map.containsKey(key))
return map.put(key, value);
else
return map.get(key);
在多线程环境中,这是竞争条件。您必须使用ConcurrentHashMap.putIfAbsent(K key, V value)
并注意返回值,它会告诉您put操作是否成功。阅读文档了解更多详情。
回答评论,要求澄清为什么这是竞争条件。
想象一下,有两个线程A
,B
将分别在地图中放置两个不同的值v1
和v2
,具有相同的密钥。密钥最初不在地图中。它们以这种方式交错:
A
调用containsKey
并发现该密钥不存在,但会立即暂停。B
调用containsKey
并发现该密钥不存在,并且有时间插入其值v2
。A
恢复并插入v1
,“和平”覆盖(因为put
是线程安全的)线程B
插入的值。现在线程B
“认为”它已成功插入了自己的值v2
,但地图中包含v1
。这确实是一场灾难,因为线程B
可能会调用v2.updateSomething()
并且会“认为”地图的消费者(例如其他线程)可以访问该对象并且会看到可能是重要更新(“ :此访客IP地址正在尝试执行DOS,拒绝从现在开始的所有请求“)。相反,该对象将很快被垃圾收集和丢失。
答案 1 :(得分:16)
它是线程安全的。但是,它的线程安全方式可能不是您所期望的。你可以看到一些“提示”:
此类与程序中的
Hashtable
完全可互操作 依赖于其线程安全但不依赖于其同步细节
要以更完整的图片了解整个故事,您需要了解ConcurrentMap
界面。
原始Map
提供了一些非常基本的读/更新方法。即使我能够实现Map
的线程安全实现;有很多情况下人们不考虑我的同步机制就无法使用我的Map。这是一个典型的例子:
if (!threadSafeMap.containsKey(key)) {
threadSafeMap.put(key, value);
}
这段代码不是线程安全的,即使地图本身也是如此。同时调用containsKey()
的两个线程可能认为没有这样的密钥,因此它们都插入Map
。
为了解决问题,我们需要明确地进行额外的同步。假设我的Map的线程安全性是通过同步关键字实现的,您需要这样做:
synchronized(threadSafeMap) {
if (!threadSafeMap.containsKey(key)) {
threadSafeMap.put(key, value);
}
}
此类额外代码需要您了解地图的“同步详细信息”。在上面的例子中,我们需要知道同步是通过“synchronized”实现的。
ConcurrentMap
界面更进了一步。它定义了一些涉及多个地图访问的常见“复杂”操作。例如,上面的示例公开为putIfAbsent()
。通过这些“复杂”操作,ConcurrentMap
的用户(在大多数情况下)不需要使用对地图的多次访问来同步操作。因此,Map的实现可以执行更复杂的同步机制以获得更好的性能。 ConcurrentHashhMap
就是一个很好的例子。事实上,线程安全是通过为地图的不同分区保留单独的锁来维护的。它是线程安全的,因为对映射的并发访问不会破坏内部数据结构,或导致任何更新丢失意外等。
考虑到上述所有因素,Javadoc的含义将更加清晰:
“检索操作(包括get)通常不会阻止”因为ConcurrentHashMap
没有使用“synchronized”来保证其线程安全性。 get
本身的逻辑关注线程安全性;如果你在Javadoc中进一步观察:
该表在内部分区以尝试允许指定的数字 没有争用的并发更新
检索非阻塞,甚至更新都可以同时发生。但是,非阻塞/并发更新并不意味着它是线程UNsafe。它只是意味着除了简单的“同步”之外,它还使用了一些其他方法来保证线程安全。
但是,由于未公开内部同步机制,如果您想执行除ConcurrentMap
提供的操作之外的一些复杂操作,您可能需要考虑更改逻辑,或者考虑不使用{{1} }。例如:
ConcurrentHashMap
答案 2 :(得分:9)
ConcurrentHashmap.get()
是线程安全的
ConcurrentModificationException
Map
也是如此。答案 3 :(得分:7)
HashMap
根据hashCode
划分为"buckets"。 ConcurrentHashMap
使用了这个事实。它的同步机制基于阻塞桶而不是整个Map
。这样很少有线程可以同时写入几个不同的桶(一个线程可以一次写入一个桶)。
从ConcurrentHashMap
读取几乎不使用同步。在获取密钥值时使用同步,它会看到null
值。由于ConcurrentHashMap
无法将null
存储为值(是的,除了键,值也不能是null
s),因此建议在阅读时提取null
另一个线程初始化map entry (键值对)的中间:当指定了key,但是值还没有,并且它仍然保持 default null。
在这种情况下,读取线程需要等到输入完全写入。
read()
的结果将基于当前的地图状态。如果您读取了更新过程中的键值,则可能会因为写入过程尚未完成而获得旧值。
答案 4 :(得分:5)
ConcurrentHashMap中的get()是线程安全的,因为它读取了值 这是挥发性的。如果任何键的值为null,则为 get()方法等到它获得锁定然后它读取更新的 值。
当put()
方法更新CHM时,它会将该键的值设置为null,然后创建一个新条目并更新CHM。 get()
方法使用此空值作为另一个线程使用相同密钥更新CHM的信号。
答案 5 :(得分:4)
这只是意味着当一个线程正在更新并且一个线程正在读取时,不能保证首先及时调用ConcurrentHashMap方法的线程将首先执行它们的操作。
考虑项目的更新,告诉鲍勃在哪里。如果一个线程询问Bob所处的位置,而另一个线程在更新时说他“进入”内部,则无法预测读者线程是否会将Bob的状态设置为“内部”或“外部”。即使更新线程首先调用该方法,读者线程也可能会获得“外部”状态。
线程不会引起彼此的问题。代码是ThreadSafe。
一个线程不会进入无限循环或开始生成奇怪的NullPointerExceptions,或者使用旧状态的一半和新状态的一半来获得“它”。