ConcurrentHashMap与同步

时间:2019-05-21 12:24:23

标签: java concurrency

我正在维护一些旧代码,并在synchronized上使用ConcurrentHashMap关键字找到了一些实现。对我来说似乎不必要:

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键是否合理?还是那是两个不同的东西?

4 个答案:

答案 0 :(得分:2)

ConcurrentHashMap同步每个单独的方法调用本身,以便其他线程无法访问该映射(并可能破坏该映射的内部数据结构)。

Synchronized块可同步两个或多个连续的方法调用,从而使其他线程无法修改调用之间的数据结构(就应用程序逻辑而言,这可能会破坏数据的一致性)。

请注意,只有在使用相同监视对象从同步块执行对HashMap的所有访问的情况下,synchornized块才起作用。

答案 1 :(得分:2)

这是必要的,因为多个线程可能会尝试同时附加到同一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);

答案 2 :(得分:2)

在这种情况下:

    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调用)。这意味着存在内存危险,并且在ConcurrentModificationException方法中可能toString()是可能的。

  • else分支中,size()调用正在访问子图中每个ArrayList中的size字段。如果不在子图级别进行同步,则这些列表之一可能同时存在add。这可能导致size()方法返回陈旧的值。此外,在迭代时,不能保证您会看到添加到子地图的地图条目。如果这些事件中的任何一个发生,则sum可能不正确。 (是否真的有问题取决于此方法的要求:不正确的计数 是可以接受的。)

答案 3 :(得分:1)

其他答案还不够充分……

Schema::create('users', function (Blueprint $table) {
                $table->increments('id');
                $table-> string('name');
                $table-> string('email')->unique();
                $table-> string('password');
                $table->rememberToken();
                $table->timestamps();
                $table -> string('first_name') -> nullable();
                $table -> string('middle_name') -> nullable();
                $table -> string('last_name') -> nullable();
                $table -> string('city') -> nullable();
                $table -> integer('role') -> unsigned();
            });

有必要吗?很难说。

什么是 for(Map<String, List<String>> map: mapsList){ synchronized(map){ // <-- is it necessary? if(/*condition*/){ ...iterate over map... } else { ...iterate over map... } } } ?在线程/*condition*/上进行同步之后,但在线程B执行两个分支中的任何一个之前或同时,在线程map上进行同步是否会阻止其他线程A更改/*condition*/的值?如果是这样,那么synchronized块可能非常重要。

那些迭代怎么样?在线程map上进行同步是否可以防止其他一些线程A在迭代线程B时更改映射的内容?如果是这样,那么synchronized块可能非常重要。