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并抛出NullPointerExceptionexception,因为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作为映射值。

你同步的方式是正确的。 但有一个问题

  1. 由Collection框架提供的同步包装确保方法调用add / get / contains将会互斥。

然而,在现实世界中,您通常会在input值之前查询地图。 因此,你需要做两个操作,因此需要一个同步块。 所以你用它的方式是正确的。 然而。

  1. 您可以使用Collection框架中可用的Map的并发实现。 “ConcurrentHashMap”的好处是

一个。 它有一个API“putIfAbsent”,它可以做更多的工作,但效率更高。

湾 它的高效:CocurrentMap只是locking键,因此它不会阻塞整个地图的世界。 在那里你已经阻止了键和值。

C。 你可能已经把你的地图对象的引用传递给你的代码库中的其他地方,在这个地方你/你的其他开发者可能最终会错误地使用它。 也就是说,他可能只是添加()或得到()没有locking在地图的对象。 因此,他的电话将不会相互排斥你的同步块。 但是使用并发的实现可以让你安心,永远不会被错误地使用/实现。