Hashtable和Collections.synchronizedMap是线程安全的,但仍然是复合操作,如
if (!map_obj.containsKey(key)) {
map_obj.put(key, value);
}
需要外部同步:
synchronized(map_obj) {
if (!map_obj.containsKey(key)) {
map_obj.put(key, value);
}
}
假设我们有ConcurrentHashMap(CHM)而不是Hashtable或HashMap。 CHM为上述复合操作提供了另一种putIfAbsent()
方法,因此无需外部同步。
但假设CHM没有提供putIfAbsent()
。然后我们可以编写以下代码:
synchronized(concurrenthashmap_obj) {
if (!concurrenthashmap_obj.containsKey(key)) {
concurrenthashmap_obj.put(key, value);
}
}
我的意思是我们可以在CHM对象上使用外部同步吗?它会起作用吗?
对于上述复合操作,CHM中有putIfAbsent()
方法,但如果我们使用CHM,如何为其他复合操作实现线程安全性。我的意思是我们可以在CHM对象上使用外部同步吗?
答案 0 :(得分:17)
不,您不能使用外部同步来确保复合操作的原子性超过ConcurrentHashMap
。
准确地说,您可以使用外部同步来确保复合操作的原子性,但前提是ConcurrentHashMap
的所有操作都在同一个锁上同步(尽管使用ConcurrentHashMap
不会在这种情况下有意义 - 您可以用常规HashMap
替换它。
使用外部同步的方法仅适用于Hashtable
和Collections.synchronizedMap()
,因为它们保证它们的原始操作对synchronized
也适用于这些对象。由于ConcurrentHashMap
没有提供这样的保证,原始操作可能会干扰复合操作的执行,从而破坏其原子性。
但是,ConcurrentHashMap
提供了许多可用于以乐观方式实现复合操作的方法:
putIfAbsent(key, value)
remove(key, value)
replace(key, value)
replace(key, oldValue, newValue)
您可以使用这些操作来实现某些复合操作,而无需进行明确的同步,就像您对AtomicReference
所做的那样,等等。
答案 1 :(得分:6)
没有任何理由你不能。传统同步适用于所有内容,没有针对它们的特殊例外。 ConcurrentHashMaps只是使用更优化的线程安全机制,如果你想做更复杂的事情,回退到传统的同步可能实际上是你唯一的选择(那和使用锁)。
答案 2 :(得分:6)
您始终可以使用synchronized
块。 java.util.concurrent
中的花哨集合并没有禁止它,它们只是使它成为大多数常见用例的冗余。如果您正在执行复合操作(例如 - 您要插入两个必须始终具有相同值的键),不仅可以您使用外部同步 - 您必须。
E.g:
String key1 = getKeyFromSomewhere();
String key2 = getKeyFromSomewhereElse();
String value = getValue();
// We want to put two pairs in the map - [key1, value] and [key2, value]
// and be sure that in any point in time both key1 and key2 have the same
// value
synchronized(concurrenthashmap_obj) {
concurrenthashmap_obj.put(key1, value);
// without external syncronoziation, key1's value may have already been
// overwritten from a different thread!
concurrenthashmap_obj.put(key2, value);
}
答案 3 :(得分:3)
当ConcurrentHashMap实现Map接口时,它确实支持每个基本Map都具有的所有功能。所以是的:你可以像任何其他地图一样使用它并忽略所有额外的功能。但那时你基本上会有一个较慢的HashMap。
同步Map和并发Map之间的主要区别在于 - 正如名称所示 - 并发。想象一下,如果您synchronize
阻止了99个线程,并且1个可以完成工作,那么您有100个想要从地图中读取的线程。如果您使用并发100个线程可以同时 。
现在,如果您考虑使用线程的实际原因,您很快就会得出结论:您应该摆脱所有可能的synchronized
块。
答案 4 :(得分:1)
这完全取决于你的意思"其他复合操作"通过"工作"。同步与ConcurrentHashMap的工作方式与其他任何对象的工作方式完全相同。
因此,如果您希望将某些复杂的共享状态更改视为原子更改,则必须在同一锁上同步对此共享状态的所有访问。这个锁可以是Map本身,也可以是另一个对象。
答案 5 :(得分:-1)
java.util.concurrent.ConcurrentHashMap
“在依赖于线程安全而不依赖于其同步细节的程序中与Hashtable完全可互操作:它们不会抛出ConcurrentModificationException。”
“允许更新操作”
一般来说,从同步的角度来看,读取是安全的,但不是内存的立场。
另请参阅“http://www.ibm.com/developerworks/java/library/j-jtp03304/”。
因此synchronizaton
和volatile
应该用于管理并发阅读(与写作相比)。
putIfAbsent
是您的friend:
如果指定的键尚未与值关联,则关联 它与给定的
value. This is equivalent to if (!map.containsKey(key)) return map.put(key, value); else return map.get(key);
除了动作!!!原子地执行!!!。