2013-02-19 116 views
31

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

“不过,尽管所有操作都是线程安全的,检索 操作不意味着锁定,并没有对 锁定整个表以防止所有访问的方式提供任何支持。该类 是在依靠其 线程安全,但不是它的同步信息的程序与Hashtable的完全的互操作性。

检索操作(包括get)通常不会阻塞,因此,可以 与更新操作交迭(包括put和remove)。反演 反映了最近compl的结果eted更新操作 在他们发病时举行。“

+0

如果我正在阅读该权限,这意味着检索将始终返回上次更新的结果以在检索开始时完成。这意味着完成更新可以在检索开始和完成之间发生,并且不会改变所述检索的结果。 – Aurand 2013-02-19 00:29:53

回答

8

ConcurrentHashmap.get()是线程安全的,在这个意义上,

  • 它不会引发任何异常,包括ConcurrentModificationException
  • 它会返回一个结果是,在过去的一些(近期的)时间是真实的。这意味着两个背对背的调用可以返回不同的结果。当然,任何其他Map也是如此。
+0

“两次背靠背打电话”是什么意思? – user697911 2013-02-19 00:28:40

+0

调用'get()'并保存结果。再次调用'get()'并比较结果。这就是“背对背”的意思。 – 2013-02-19 00:31:47

+2

如果存在介入修改映射的put()或其他操作,则返回调用以获取可以返回不同的结果。 – Gray 2013-02-19 00:44:08

4

这只是意味着当一个线程正在更新并且一个线程正在读取时,不能保证首先及时调用ConcurrentHashMap方法的那个将首先发生它们的操作。

想想关于该项目的更新,告诉鲍勃在哪里。如果一个线程询问Bob是在什么时候另一个线程更新以表示他进入内部的同一时间,则无法预测读者线程是否将Bob的状态视为“内部”或“外部”。即使更新线程首先调用方法,阅读器线程可能会获得“外部”状态。

线程不会导致对方的问题。代码是ThreadSafe。

一个线程不会进入无限循环或开始生成更奇怪的NullPointerExceptions,或者获得一半旧状态和一半新状态的“itside”。

+0

[双重锁定](https://en.wikipedia.org/wiki/Double-checked_locking)模式是否可以解决问题?通过强制寻找'内部'值的线程检查两次,作者线程是否有机会更新值?这样你会阻止写入但不读取? “ – 2014-03-06 10:21:11

33

get()方法是线程安全的,其他用户给你有关这个特定问题的有用答案。

然而,尽管ConcurrentHashMap是线程安全的投递替代HashMap,它认识到,如果你是做多操作可能需要显著改变你的代码是很重要的。例如,请输入以下代码:

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

在多线程环境中,这是一种竞争条件。你必须使用ConcurrentHashMap.putIfAbsent(K key, V value)并注意返回值,它告诉你put操作是否成功。阅读文档以获取更多细节。


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

想象有两个线程AB那些打算把两个不同的值在地图上,v1v2分别具有相同的密钥。密钥最初不在地图中。他们交织在这样:

  • 主题A电话containsKey并找出该键不存在,而是立即停用。
  • 线程B调用containsKey并发现该密钥不存在,并且有时间插入其值v2
  • 线程A恢复并插入v1“安静”覆盖(因为put是线程安全的)由线程B插入的值。

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

+0

”不是线程安全的HashMap替代插件“。呃,当然是。你说对多个操作有竞争条件是正确的,但是这并不能阻止'ConcurrentHashMap'线程安全。 – Gray 2013-02-19 00:42:31

+0

对于插入式替换,我的意思是:将您的HashMap更改为ConcurrentHashMap,并在地图上执行所有常见操作(如手工“放置如果不存在” - 我已经看到过无数次)都是自动线程安全的。 – gd1 2013-02-19 00:43:40

+0

同意。但是,您的声明“ConcurrentHashMap不是线程安全的HashMap替代插件”,至少是极其令人误解的。我会说这是不正确的。 – Gray 2013-02-19 00:45:13

1

HashMap根据hashCode分为"buckets"ConcurrentHashMap使用这个事实。其同步机制基于阻塞桶而不是整个Map。通过这种方式,几个线程可以同时写入几个不同的存储区(一次一个线程可以写入一个存储区)。

ConcurrentHashMap读取差不多不使用同步机制。我之所以说几乎是因为它使用同步时,它会得到null。基于事实ConcurrentHashMap不能存储null值(是的,值也不能为空值),如果它将获得该值,它必须意味着在编写键值对中间调用读取(创建条目后关键,但其价值尚未确定 - 这是null)。在这种情况下,阅读线程需要等待写入完成。

因此read()的结果将基于当前的地图状态。如果您阅读正在更新过程中的密钥的值,则由于写入过程尚未完成,您可能会获得旧值。

9

它是线程安全的。然而,它是线程安全的方式可能不是你所期望的。还有一些“线索”,你可以看到:

这个类是完全的互操作性与Hashtable在 依赖于它的线程安全程序,但不是它的同步信息

要知道整个故事在更完整的图片中,您需要了解接口ConcurrentMap

原来Map提供了一些非常基本的读/更新方法。即使我能够做一个线程安全的执行Map;没有考虑我的同步机制,人们无法使用我的地图的情况很多。这是一个典型的例子:

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

这段代码不是线程安全的,即使地图本身是。两个线程在同一时间调用containsKey()可能会认为没有这样的密钥,因此它们都插入到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以外的复杂操作,您可能需要考虑更改逻辑或考虑不使用ConcurrentHashMap。例如:

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

的get()中的ConcurrentHashMap是线程安全的,因为它读取值 这是挥发。并且在任何键的值为空的情况下,然后 get()方法将等待,直到它获得锁定,然后它读取更新的 值。

put()方法正在更新CHM,然后它将该键的值设置为空,然后它创建一个新条目并更新CHM。该空值被get()方法用作另一线程正在用相同密钥更新CHM的信号。