首页 > 解决方案 > 为什么 ConcurrentHashMap 中的 computeIfAbsent() 方法行为不一致?

问题描述

我有一个在 Apache Tomcat 9 上运行的 Java 8 Web 应用程序。ConcurrentHashMap'computeIfAbsent()方法的调用没有返回或返回时间过长。

在下面给出的代码中,打印了“ Adding to Map ”行,而“ Map: ”行在某些情况下根本不打印,就好像正在执行的线程被困在方法中一样。一旦它被困住,任何对相同方法的后续调用id也会被卡住并且永远不会返回,而id立即调用不同的返回值。在另一个实例上测试不同id的 ,该computeIfAbsent()方法在 2 分钟后返回。测试时执行代码的最大并发调用数仅为 20 左右。据我了解computeIfAbsent()是线程安全的。这里有什么问题?

private Map<String, Map<String, SomeClass>> aMap = new ConcurrentHashMap<>();
LOGGER.debug("Adding to Map");
Map<String, SomeClass> m = aMap
            .computeIfAbsent(id, k -> Collections.synchronizedMap(new HashMap<>()));
LOGGER.debug("Map : " + m);

标签: javaconcurrenthashmap

解决方案


对具有相同 id 的同一方法的任何后续调用也被卡住并且永远不会返回,而具有不同 id 的调用立即返回?

是的,如果计算正在进行中,该 ID 的任何后续计算调用都将被阻止

如果指定的键尚未与值关联,则尝试使用给定的映射函数计算其值并将其输入到此映射中,除非为空。整个方法调用以原子方式执行,因此每个键最多应用一次该函数。其他线程在此映射上的某些尝试更新操作可能会在计算进行时被阻止,因此计算应该简短而简单,并且不得尝试更新此映射的任何其他映射。

测试时执行代码的最大并发调用数仅为 20 左右。根据我的理解?

不,这完全取决于该地图中有多少可用桶

ConcurrentHashMap中,一次任意数量的线程都可以执行检索操作,但是对于对象的更新,线程必须锁定线程要操作的特定段。这种类型的锁定机制称为段锁定或桶锁定。因此,一次可以执行 16 次更新操作

computeIfAbsent() 是线程安全的吗?

是的,它是线程安全的ConcurrentHashMap

支持检索的完全并发和更新的高预期并发的哈希表。此类遵循与 Hashtable 相同的功能规范,并包含与 Hashtable 的每个方法对应的方法版本。但是,即使所有操作都是线程安全的,检索操作也不需要锁定,并且不支持以阻止所有访问的方式锁定整个表。在依赖线程安全但不依赖同步细节的程序中,此类与 Hashtable 完全可互操作。

老实说,我不是设计和实现的人ConcurrentHashMap,但通过互联网我找到了一篇关于 java 8 ConcurrentHashMap 改进的文章,我认为这可能会导致第一次调用的延迟。

在第一次使用之前最小化占用空间的惰性表初始化


推荐阅读