您的代码中可能存在细微的错误。
[更新:因为他正在使用map.remove(),所以这种描述并不完全有效。 我第一次错过了这个事实。 :(感谢问题的作者指出这一点。我将其余部分保留原样,但改变了主要声明,说有可能存在错误。]
在doWork()中,您可以以线程安全的方式从Map获取List值。 然而,之后,您在不安全的情况下访问该列表。 例如,一个线程可能正在使用doWork()中的列表,而另一个线程在addToMap()中调用synchronizedMap.get(key).add(value)。 这两个访问不同步。 经验法则是集合的线程安全保证不会扩展到它们存储的键或值。
你可以通过在地图中插入一个同步列表来解决这个问题
List valuesList = new ArrayList();
valuesList.add(value);
synchronizedMap.put(key, Collections.synchronizedList(valuesList)); // sync'd list
或者,您可以在doWork()中访问列表时在地图上进行同步:
public void doWork(String key) {
List values = 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的这句精彩报价总能帮助我设计调试多线程程序。
对于可由多个线程访问的每个可变状态变量,必须在保持相同锁的情况下执行对该变量的所有访问。