它是线程安全的。然而,确保线程安全的方式可能与您期望的方式不同。您可以从以下"提示"中看到:
在依赖于其线程安全性但不依赖于其同步细节的程序中,此类与 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的实现可以执行更复杂的同步机制以获得更好的性能。 ConcurrentHashMap
就是一个很好的例子。它是线程安全的,因为对Map的并发访问不会破坏内部数据结构或导致任何意外的更新丢失等问题。
考虑到以上所有内容,Javadoc的含义也将更加清晰:
“检索操作(包括get)通常不阻塞”,因为ConcurrentHashMap
不使用“synchronized”来确保线程安全性。 get
本身的逻辑会处理线程安全性;如果您进一步查看Javadoc:
表被分区以尝试允许指定数量的并发更新而没有竞争
不仅检索是非阻塞的,即使更新也可以同时发生。但是,非阻塞/并发更新并不意味着它是线程不安全的。这只是意味着它使用了一些方式来实现线程安全性,而不是简单的“synchronized”。
然而,因为内部同步机制未公开,如果您想要进行除ConcurrentMap
提供的操作之外的某些复杂操作,则可能需要考虑更改您的逻辑或考虑不使用ConcurrentHashMap
。例如:
// only remove if both key1 and key2 exists
if (map.containsKey(key1) && map.containsKey(key2)) {
map.remove(key1);
map.remove(key2);
}