ConcurrentHashMap: avoid extra object creation with “putIfAbsent”?

后端 未结 7 2065
北海茫月
北海茫月 2020-12-12 19:10

I am aggregating multiple values for keys in a multi-threaded environment. The keys are not known in advance. I thought I would do something like this:

class         


        
7条回答
  •  被撕碎了的回忆
    2020-12-12 19:49

    This is a problem I also looked for an answer. The method putIfAbsent does not actually solve the extra object creation problem, it just makes sure that one of those objects doesn't replace another. But the race conditions among threads can cause multiple object instantiation. I could find 3 solutions for this problem (And I would follow this order of preference):

    1- If you are on Java 8, the best way to achieve this is probably the new computeIfAbsent method of ConcurrentMap. You just need to give it a computation function which will be executed synchronously (at least for the ConcurrentHashMap implementation). Example:

    private final ConcurrentMap> entries =
            new ConcurrentHashMap>();
    
    public void method1(String key, String value) {
        entries.computeIfAbsent(key, s -> new ArrayList())
                .add(value);
    }
    

    This is from the javadoc of ConcurrentHashMap.computeIfAbsent:

    If the specified key is not already associated with a value, attempts to compute its value using the given mapping function and enters it into this map unless null. The entire method invocation is performed atomically, so the function is applied at most once per key. Some attempted update operations on this map by other threads may be blocked while computation is in progress, so the computation should be short and simple, and must not attempt to update any other mappings of this map.

    2- If you cannot use Java 8, you can use Guava's LoadingCache, which is thread-safe. You define a load function to it (just like the compute function above), and you can be sure that it'll be called synchronously. Example:

    private final LoadingCache> entries = CacheBuilder.newBuilder()
            .build(new CacheLoader>() {
                @Override
                public List load(String s) throws Exception {
                    return new ArrayList();
                }
            });
    
    public void method2(String key, String value) {
        entries.getUnchecked(key).add(value);
    }
    

    3- If you cannot use Guava either, you can always synchronise manually and do a double-checked locking. Example:

    private final ConcurrentMap> entries =
            new ConcurrentHashMap>();
    
    public void method3(String key, String value) {
        List existing = entries.get(key);
        if (existing != null) {
            existing.add(value);
        } else {
            synchronized (entries) {
                List existingSynchronized = entries.get(key);
                if (existingSynchronized != null) {
                    existingSynchronized.add(value);
                } else {
                    List newList = new ArrayList<>();
                    newList.add(value);
                    entries.put(key, newList);
                }
            }
        }
    }
    

    I made an example implementation of all those 3 methods and additionally, the non-synchronized method, which causes extra object creation: http://pastebin.com/qZ4DUjTr

提交回复
热议问题