English 中文(简体)
同时HashMap: 避免使用“ putifAbout” 创建额外的对象?
原标题:ConcurrentHashMap: avoid extra object creation with "putIfAbsent"?

我在一个多轨环境中将键的多个值归并在一起。 密钥事先不为人所知。 我想这样做:

class Aggregator {
    protected ConcurrentHashMap<String, List<String>> entries =
                            new ConcurrentHashMap<String, List<String>>();
    public Aggregator() {}

    public void record(String key, String value) {
        List<String> newList =
                    Collections.synchronizedList(new ArrayList<String>());
        List<String> existingList = entries.putIfAbsent(key, newList);
        List<String> values = existingList == null ? newList : existingList;
        values.add(value);
    }
}

我看到的问题是,每当这种方法运行时,我都需要创建一个新的 ArrayList 实例,然后我把它扔掉(在多数情况下),这似乎毫无道理地滥用垃圾收集器。有没有一种更好、更安全的初始化这种结构的方法,不需要同步 record 方法?我有点惊讶的是,决定让 putif-absent 方法不返回新创建的元素,也没有办法推迟即时处理,除非需要(这样发言) 。

最佳回答

Java 8为迎合这一确切问题引入了API, 提出一线解决方案:

public void record(String key, String value) {
    entries.computeIfAbsent(key, k -> Collections.synchronizedList(new ArrayList<String>())).add(value);
}

爪哇7:

public void record(String key, String value) {
    List<String> values = entries.get(key);
    if (values == null) {
        entries.putIfAbsent(key, Collections.synchronizedList(new ArrayList<String>()));
        // At this point, there will definitely be a list for the key.
        // We don t know or care which thread s new object is in there, so:
        values = entries.get(key);
    }
    values.add(value);
}

这是弹出 Conv流HashMap 时的标准代码模式。

特殊方法 < a href=>" http://docs.oracle.com/javase/6/docs/api/java/ util/ contil/ ControlMap.html#putifAbsent%28K,%20V%29" rel="noreferr"\\code>putifAbsent(K,V) 将您的价值对象放入其中,或者收到另一条线索,然后忽略您的值对象。无论哪种方式,在调用putif-absent(K,V) 之后, get(keys)(key) 保证在线索之间保持一致,因此上述代码是安全的。

唯一浪费的间接费用是,如果某些其他线索同时为同一密钥添加一个新的条目:您“它们可能”最终会丢弃新创造的价值,但只有在还没有条目“它们”和“它们”的情况下,才会出现你线索丢失的竞赛,而这种竞赛通常是罕见的。

问题回答

从 Java-8 开始, 您可以使用以下模式创建多地图 :

public void record(String key, String value) { entries.computeIfAbsent(key, k -> Collections.synchronizedList(new ArrayList<String>())) .add(value); }

同时Hash-Map文件(不是一般合同)规定,对每一关键键只设定一次阵列列表,其初始成本是推迟更新,而正在为新密钥创建阵列列表:

最后,我对@ Bohemiian s 的回答进行了略微修改。 他提议的解决方案将 values 变量与 putifutif-Absent 调用重叠, 这造成了与我以前相同的问题。 似乎起作用的代码是这样的 :

    public void record(String key, String value) {
        List<String> values = entries.get(key);
        if (values == null) {
            values = Collections.synchronizedList(new ArrayList<String>());
            List<String> values2 = entries.putIfAbsent(key, values);
            if (values2 != null)
                values = values2;
        }
        values.add(value);
    }

它没有我喜欢的那么优雅,但是它比原型好,在每次通话中都创造了一个新的ArrayList 实例。

基于 Gene s 回答创建了两个版本

public  static <K,V> void putIfAbsetMultiValue(ConcurrentHashMap<K,List<V>> entries, K key, V value) {
    List<V> values = entries.get(key);
    if (values == null) {
        values = Collections.synchronizedList(new ArrayList<V>());
        List<V> values2 = entries.putIfAbsent(key, values);
        if (values2 != null)
            values = values2;
    }
    values.add(value);
}

public  static <K,V> void putIfAbsetMultiValueSet(ConcurrentMap<K,Set<V>> entries, K key, V value) {
    Set<V> values = entries.get(key);
    if (values == null) {
        values = Collections.synchronizedSet(new HashSet<V>());
        Set<V> values2 = entries.putIfAbsent(key, values);
        if (values2 != null)
            values = values2;
    }
    values.add(value);
}

工作效果不错

这是一个我也在寻找答案的问题。 < code> puperfutifAbsent 的方法实际上并不能解决额外对象创建问题, 它只是确保其中的一个对象不会取代另一个对象。 但是线条之间的种族条件可能导致多个对象即时化。 我可以找到三个解决这个问题的解决方案( 我会遵循这个优先顺序 ):

1 - 如果您在 Java 8 上, 实现此目标的最佳方法可能是新的 < code> compututeIfAbsent 方法, 即 < code> ConcurrentMap 。 您只需要给它一个将同步执行的计算函数( 至少对于 < Code> ConventHashMap 执行来说是这样 )。 例如 :

private final ConcurrentMap<String, List<String>> entries =
        new ConcurrentHashMap<String, List<String>>();

public void method1(String key, String value) {
    entries.computeIfAbsent(key, s -> new ArrayList<String>())
            .add(value);
}

这是来自Convol-HashMap.computeif-Absense 的javadoc:

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- 如果无法使用 Java 8, 您可以使用 Guava s LoadingCache , 这是线索安全的。 您可以对它定义一个负载函数( 就像上面的 compute 函数), 您可以确定它会同步被命名 。 例如 :

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

public void method2(String key, String value) {
    entries.getUnchecked(key).add(value);
}

3 - 如果您也不能使用瓜瓦, 您可以手动同步, 并进行双重检查的锁定。 例如 :

private final ConcurrentMap<String, List<String>> entries =
        new ConcurrentHashMap<String, List<String>>();

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

我举例说明了所有这三种方法以及非同步方法的实施情况,这些方法导致产生额外的物体: