我遇到了以下代码,并注意到一些不一致 - 对于多线程安全代码。
Map<String,Map<String,Set<String>> clusters = new HashMap<.........>;
Map<String,Set<String>> servers = clusters.get(clusterkey);
if(servers==null){
synchronized(clusterkey){
servers = clusters.get(clusterkey);
if(servers==null){....initialize new hashmap and put...}
}
}
Set<String> users=servers.get(serverkey);
if(users==null){
synchronized(serverkey){
users=servers.get(serverkey);
if(users==null){ ... initialize new hashset and put...}
}
}
users.add(userid);
这里只是一些观察:
String
is a very bad idea - > clusterKey
和serverKey
的同步可能不会按照预期的方式工作。ConcurrentHashMap
s和ConcurrentHashSet
s。虽然没有更多的背景,但实际上不可能回答这个问题。似乎代码作者想要安全地为每个clusterKey
和serverKey
创建一个映射,因此用户只需添加一次。
一个(可能更好)的方式是在synchronize
地图本身上只有clusters
然后你是安全的,因为只有一个线程可以读取和/或写入所述地图。
另一种方法是使用自定义Lock
s,可能一个用于阅读,另一个用于写入,但如果一个线程写入Map
而另一个线程正在从中读取该确切值,则可能再次导致不一致。
该代码看起来像Double checked locking idiom的非思想版本,有时用于延迟初始化。阅读提供的链接,了解为什么这是一个非常糟糕的实现。
给定代码的问题是它间歇性地失败。当有多个线程尝试使用相同的键(或具有相同哈希码的键)在地图上工作时存在竞争条件,这意味着首先创建的映射可能被第二个散列映射替换。
1 - 同步试图避免这两个线程同时在该Map中创建一个新条目。第二个必须等待,所以他的(servers==null)
也不会返回true
。
2 - users
列表似乎超出范围,但似乎不需要同步。也许程序员知道没有重复的userIds,或者他可能不关心一次又一次地重置同一个用户。
也许3- ConcurrentHashMap?