[英]ConcurrentHashMap with synchronized
我正在维护一些旧代码,并在ConcurrentHashMap
上找到了一些使用synchronized
关键字的实现。 对我来说似乎没有必要:
public class MyClass{
private final Map<MyObj, Map<String, List<String>>> conMap = new ConcurrentHashMap<>();
//...
//adding new record into conMap:
private void addToMap(MyObj id, String name, String value){
conMap.putIfAbsent(id, new ConcurrentHashMap<>());
Map<String, List<String>> subMap = conMap.get(id);
synchronized(subMap){ // <-- is it necessary?
subMap.putIfAbsent(name, new ArrayList<>());
subMap.get(name).add(value);
}
}
//...
public void doSomthing((MyObj id){
List<Map<String, List<String>>> mapsList = new LinkedList<>();
for(MyObj objId: conMap.keySet()){
if(objId.key1.equals(id.key1)){
mapsList.add(conMap.get(objId));
}
}
for(Map<String, List<String>> map: mapsList){
synchronized(map){ // <-- is it necessary?
if(timeout <= 0){
log(map.size());
for(List<String> value: map.values(){
log(id, value);
}
}
else{
int sum = 0;
for(map.Entry<String, List<String>> val: map.entrySet()){
sum += val.getValue().size();
}
log(sum);
map.wait(timeout);
}
}
//...
}
那么,在已经并发的对象上使用synchronized
密钥是否合理? 还是那是两个不同的东西?
ConcurrentHashMap同步每个单独的方法调用本身,以便其他线程无法访问该映射(并可能破坏该映射的内部数据结构)。
同步块可以同步两个或多个连续的方法调用,以便其他线程无法修改调用之间的数据结构(就应用程序逻辑而言,可能会破坏数据的一致性)。
请注意,仅当使用相同的监视对象从同步块执行对HashMap的所有访问时,synchornized块才起作用。
这是必要的,因为多个线程可能会尝试同时附加到同一ArrayList
。 该synchonized
是一种防止这种情况发生的ArrayList
显然是不同步的。
从Java 8开始,我们有了computeIfAbsent
,这意味着可以简化执行操作的puts和gets。 我会这样写,不需要同步:
conMap.computeIfAbsent(id, k -> new ConcurrentHashMap<>())
.computeIfAbsent(name, k -> new CopyOnWriteArrayList<>()) // or other thread-safe list
.add(value);
在这种情况下:
synchronized(subMap){ // <-- is it necessary?
subMap.putIfAbsent(name, new ArrayList<>());
subMap.get(name).add(value);
}
synchronized
是必要的。 没有它,您可能会有两个线程同时更新同一ArrayList
实例。 由于ArrayList
不是线程安全的,因此addToMap
方法也不是线程安全的。
在这种情况下:
synchronized(map){ // <-- is it necessary?
if(/*condition*/){
log(map.size());
for(List<String> value: map.values(){
log(id, value);
}
}
else{
int sum = 0;
for(map.Entry<String, List<String>> val: map.entrySet()){
sum += val.getValue().size();
}
log(sum);
map.wait(timeout);
}
synchronized
是必要的。
在if
分支中, log
方法(或从中调用的方法)可能会调用ArrayList::toString
,它将迭代每个ArrayList
。 如果没有在子图级别进行同步,则另一个线程可能会同时进行add
(例如, addToMap
调用)。 这意味着存在内存危险,并且toString()
方法中可能会出现ConcurrentModificationException
。
在else
分支中, size()
调用正在访问子映射中每个ArrayList
中的size字段。 如果不在子图级别进行同步,则可能会在这些列表之一上同时add
。 这可能导致size()
方法返回陈旧的值。 此外,在迭代时,不能保证您会看到添加到子地图的地图条目。 如果这些事件之一发生,则sum
可能不准确。 (无论这是一个真正的问题取决于该方法的要求:计数错误是可以接受的)
其他答案还不够充分...
for(Map<String, List<String>> map: mapsList){
synchronized(map){ // <-- is it necessary?
if(/*condition*/){
...iterate over map...
}
else {
...iterate over map...
}
}
}
有必要吗? 很难说。
什么是/*condition*/
? 在线程B测试之后,但在线程B执行两个分支中的任何一个之前或同时,在map
同步是否会阻止其他线程A更改/*condition*/
的值? 如果是这样,则synchronized
块可能非常重要。
那些迭代怎么样? 是否在同步map
防止一些其他的线程A从改变地图的内容,但线程B迭代? 如果是这样,则synchronized
块可能非常重要。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.