我正在多线程环境中聚合键的多个值。 key 未知。我以为我会做这样的事情:

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的新实例,然后将其丢弃(在大多数情况下)。这似乎是对垃圾收集器的不合理滥用。有没有一种更好的,线程安全的方法来初始化这种结构而无需synchronize record方法?我对是否让putIfAbsent方法不返回新创建的元素的决定感到惊讶,并且对缺少除非被要求(可以这么说)延迟实例化的方法也感到惊讶。

最佳答案

Java 8引入了一个API来解决这个确切的问题,提出了一种1行解决方案:

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

对于Java 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);
}

这是填充ConcurrentHashMap时的标准代码模式。

特殊方法 putIfAbsent(K, V)) 会将您的值对象放入其中,或者如果在您之前有另一个线程,则它将忽略您的值对象。无论哪种方式,在调用putIfAbsent(K, V))之后,get(key)都被保证在线程之间是一致的,因此上述代码是线程安全的。

唯一浪费的开销是,如果其他某个线程在同一时间为同一键同时添加新条目:您可能最终会丢弃新创建的值,但这只会在尚无条目且存在种族冲突的情况下发生线程丢失,这种情况通常很少见。

09-15 11:36