java - ConcurrentHashMap:避免使用 "putIfAbsent"创建额外的对象?

标签 java synchronization thread-safety concurrenthashmap

我正在为多线程环境中的键聚合多个值。 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 来解决这个确切的问题,提供了一个单行解决方案:

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) 保证在线程之间是一致的,因此上面的代码是线程安全的。

唯一浪费的开销是如果某个其他线程同时为同一个键添加了一个新条目:您可能最终会丢弃新创建的值,但这只有在有还不是一个条目并且你的线程输掉了一场比赛,这通常很少见。

关于java - ConcurrentHashMap:避免使用 "putIfAbsent"创建额外的对象?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/10743622/

相关文章:

c++ - 使用使用 std::async 创建的线程发送 MPI 的线程安全

Java |运营商是为了什么?

java - 检测鼠标点击 SELECTION Editable ComboBox JavaFX

Java HashSet 同步查询

multithreading - 多线程环境中的单例

c# - Xamarin Forms 拉取异步表。CreateQuery() 默认添加了 OFFSET

C# 静态构造函数在填充 ConcurrentDictionary 时初始化线程安全

c# - 对静态集合的线程安全访问

java - 无法绑定(bind): field is static: form. MainForm.nameTextField

Java - 为什么我不能在 for 循环之外初始化变量的起始值?