繁体   English   中英

ConcurrentHashMap与同步

[英]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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM