以下代码是否设置为正确同步呼叫synchronizedMap
?
public class MyClass { private static Map> synchronizedMap = Collections.synchronizedMap(new HashMap >()); public void doWork(String key) { List 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 valuesList = new ArrayList (); valuesList.add(value); synchronizedMap.put(key, valuesList); } } } }
根据我的理解,我需要同步块addToMap()
来防止另一个线程调用remove()
或containsKey()
在我通过调用之前put()
但我不需要同步块doWork()
因为另一个线程无法addToMap()
在remove()
返回之前进入synchronized块因为我最初创建了Map与Collections.synchronizedMap()
.那是对的吗?有一个更好的方法吗?
Collections.synchronizedMap()
保证您要在地图上运行的每个原子操作都将被同步.
但是,必须在块中同步在地图上运行两个(或更多)操作.是的 - 您正在正确同步.
如果您使用的是JDK 6,那么您可能需要查看ConcurrentHashMap
请注意该类中的putIfAbsent方法.
还有就是潜在的代码中的一个微妙的错误.
[ 更新: 因为他正在使用map.remove(),所以这种描述并不完全有效.我第一次错过了这个事实.:(感谢问题的作者指出这一点.我将其余部分保留原样,但更改了主要声明,说有可能存在错误.]
在doWork()中,您可以以线程安全的方式从Map获取List值.然而,之后,您在不安全的情况下访问该列表.例如,一个线程可能正在使用doWork()中的列表,而另一个线程在addToMap()中调用synchronizedMap.get(key).add(value ).这两个访问不同步.经验法则是集合的线程安全保证不会扩展到它们存储的键或值.
您可以通过在地图中插入同步列表来解决此问题
ListvaluesList = new ArrayList (); valuesList.add(value); synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list
或者,您可以在doWork()中访问列表时在地图上进行同步:
public void doWork(String key) { Listvalues = null; while ((values = synchronizedMap.remove(key)) != null) { synchronized (synchronizedMap) { //do something with values } } }
最后一个选项会稍微限制并发性,但IMO会更加清晰.
另外,关于ConcurrentHashMap的快速说明.这是一个非常有用的类,但并不总是适用于同步HashMaps的替代品.引用其Javadocs,
在依赖于线程安全但不依赖于其同步细节的程序中,此类可与Hashtable完全互操作.
换句话说,putIfAbsent()非常适合原子插入,但不保证在该调用期间地图的其他部分不会改变; 它只保证原子性.在您的示例程序中,您依赖于put()s以外的(同步)HashMap的同步详细信息.
最后一件事.:)来自Java Concurrency in Practice的这句精彩报价总能帮助我设计调试多线程程序.
对于可由多个线程访问的每个可变状态变量,必须在保持相同锁的情况下执行对该变量的所有访问.
是的,您正在正确同步.我将更详细地解释这一点.只有在必须依赖于在synchronizedMap对象上的方法调用序列中的后续方法调用中的先前方法调用的结果时,才必须在synchronizedMap对象上同步两个或多个方法调用.我们来看看这段代码:
synchronized (synchronizedMap) { if (synchronizedMap.containsKey(key)) { synchronizedMap.get(key).add(value); } else { ListvaluesList = new ArrayList (); 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
会被抛出,因为synchronizedMap.get(key)
将返回null
.如果synchronizedMap
对象的方法调用序列不依赖于彼此的结果,那么您不需要同步序列.例如,您不需要同步此序列:
synchronizedMap.put(key1, valuesList1); synchronizedMap.put(key2, valuesList2);
这里
synchronizedMap.put(key2, valuesList2);
方法调用不依赖于前面的结果
synchronizedMap.put(key1, valuesList1);
方法调用(它不关心某些线程是否干扰了两个方法调用,例如已经删除了key1
).