Java同步块与Collections.synchronizedMap
以下代码是否设置为正确同步synchronizedMap上的调用?
public class MyClass { private static Map<String, List<String>> synchronizedMap = Collections.synchronizedMap(new HashMap<String, List<String>>()); public void doWork(String key) { List<String> values = null; while ((values = synchronizedMap.remove(key)) != null) { //do something with values } } public static void addToMap(String key, String value) { synchronized (synchronizedMap) { if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, valuesList); } } } }
从我的理解,我需要addToMap()
的同步块,以防止另一个线程在调用put()
之前调用remove()
或containsKey()
,但是我不需要doWork()
的同步块,因为另一个线程线程无法在remove()
返回之前在addToMap()
input同步块,因为我最初使用Collections.synchronizedMap()
创build了Map。 那是对的吗? 有没有更好的方法来做到这一点?
Collections.synchronizedMap()
保证你想要在地图上运行的每个primefaces操作都将被同步。
但是,在地图上运行两个(或更多)操作必须在块中同步。 所以是的 – 你正在同步。
如果您使用的是JDK 6,那么您可能需要检查ConcurrentHashMap
请注意该类中的putIfAbsent方法。
您的代码中可能存在微妙的错误。
[ 更新:因为他使用map.remove()这个描述是不完全有效的。 我第一次错过了这个事实。 :(感谢这个问题的作者指出了,我现在离开了其余的,但改变了主要声明,说有可能是一个错误。
在doWork()中,您可以以线程安全的方式从Map中获取List值。 然后,然而,你是在一个不安全的问题访问该列表。 例如,一个线程可能使用doWork()中的列表,而另一个线程在addToMap()中调用synchronizedMap.get(key).add(value ) 。 这两个访问不同步。 经验法则是集合的线程安全保证不会扩展到它们存储的键或值。
你可以通过在地图中插入一个同步列表来解决这个问题
List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list
或者,您可以在访问doWork()中的列表时同步地图:
public void doWork(String key) { List<String> values = null; while ((values = synchronizedMap.remove(key)) != null) { synchronized (synchronizedMap) { //do something with values } } }
最后一个选项会限制并发性,但IMO有些更清晰。
此外,有关ConcurrentHashMap的快速注释。 这是一个非常有用的类,但并不总是替代同步HashMaps。 引用Javadocs,
这个类可以在依赖线程安全性的程序中完全与Hashtable互操作, 但是不依赖于它的同步细节 。
换句话说,putIfAbsent()对于primefaces插入是非常好的,但不保证在调用期间映射的其他部分不会改变。 它只保证primefaces性。 在你的示例程序中,除了put()之外,你依赖(同步的)HashMap的同步细节。
最后一件事。 :)这个来自Java Concurrency in Practice的巨大引用总是帮助我devise一个debuggingmultithreading程序。
对于每个可被多个线程访问的可变状态variables,对该variables的所有访问必须使用相同的锁执行。
是的,你正在同步。 我将更详细地解释这一点。 只有在必须依赖之前方法调用的结果(在synchronizedMap对象的方法调用顺序中的后续方法调用中)的情况下,才必须同步synchronizedMap对象上的两个或多个方法调用。 我们来看看这个代码:
synchronized (synchronizedMap) { if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, valuesList); } }
在这个代码中
synchronizedMap.get(key).add(value);
和
synchronizedMap.put(key, valuesList);
方法调用依赖于前一个的结果
synchronizedMap.containsKey(key)
方法调用。
如果方法调用的顺序不同步,结果可能是错误的。 例如, thread 1
正在执行方法addToMap()
, thread 2
正在执行方法doWork()
synchronizedMap
对象上的方法调用顺序可能如下所示: Thread 1
执行了方法
synchronizedMap.containsKey(key)
结果是“ true
”。 之后,操作系统将执行控制切换到thread 2
并执行
synchronizedMap.remove(key)
之后,执行控制已切换回thread 1
,并执行例如
synchronizedMap.get(key).add(value);
认为synchronizedMap
对象包含key
并抛出NullPointerException
exception,因为synchronizedMap.get(key)
将返回null
。 如果synchronizedMap
对象上的方法调用顺序不依赖于对方的结果,则不需要同步顺序。 例如,你不需要同步这个序列:
synchronizedMap.put(key1, valuesList1); synchronizedMap.put(key2, valuesList2);
这里
synchronizedMap.put(key2, valuesList2);
方法调用不依赖于前面的结果
synchronizedMap.put(key1, valuesList1);
方法调用(它不关心是否某个线程干扰了两个方法调用,例如已经删除了key1
)。
这对我来说是正确的。 如果我要改变任何东西,我会停止使用Collections.synchronizedMap()并以同样的方式同步所有东西,只是为了使它更清晰。
另外,我会replace
if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { List<String> valuesList = new ArrayList<String>(); valuesList.add(value); synchronizedMap.put(key, valuesList); }
同
List<String> valuesList = synchronziedMap.get(key); if (valuesList == null) { valuesList = new ArrayList<String>(); synchronziedMap.put(key, valuesList); } valuesList.add(value);
查看Google Collections的Multimap
,例如本演示文稿的第28页。
如果由于某种原因无法使用该库,请考虑使用ConcurrentHashMap
而不是SynchronizedHashMap
; 它有一个漂亮的putIfAbsent(K,V)
方法,如果它不在那里,你可以自动添加元素列表。 此外,如果您的使用模式允许,请考虑使用CopyOnWriteArrayList
作为映射值。
你同步的方式是正确的。 但有一个问题
- 由Collection框架提供的同步包装确保方法调用add / get / contains将会互斥。
然而,在现实世界中,您通常会在input值之前查询地图。 因此,你需要做两个操作,因此需要一个同步块。 所以你用它的方式是正确的。 然而。
- 您可以使用Collection框架中可用的Map的并发实现。 “ConcurrentHashMap”的好处是
一个。 它有一个API“putIfAbsent”,它可以做更多的工作,但效率更高。
湾 它的高效:CocurrentMap只是locking键,因此它不会阻塞整个地图的世界。 在那里你已经阻止了键和值。
C。 你可能已经把你的地图对象的引用传递给你的代码库中的其他地方,在这个地方你/你的其他开发者可能最终会错误地使用它。 也就是说,他可能只是添加()或得到()没有locking在地图的对象。 因此,他的电话将不会相互排斥你的同步块。 但是使用并发的实现可以让你安心,永远不会被错误地使用/实现。