hashmap大小
最近,我偶然发现了一个错误,该错误是由于多个线程对java.util.HashMap的使用不当引起的。 该错误是泄漏抽象的一个很好的例子。 只有了解数据结构的实现级别详细信息,才能帮助我解决当前的问题。 因此,我希望与他人分享我所遇到的问题,以鼓励一些读者熟悉基本数据结构的实现方式。
在一天中,通常仅需几分钟即可完成的某些分析过程已经运行了数小时,因此,我所面对的症状每天都变得丑陋。 作为我们技术的真正信奉者,我们通过自己的监控软件及时得到通知,并开始调查原因。
我还从处理线程中获得了几个线程转储。 他们指出,该代码只是在堆转储中发现的哈希图中处理条目,看似处于未终止的循环中。 因此,似乎正在分析的数据以某种方式损坏了,其中包含循环引用。
令我惊讶的是,确实如此。 分析的堆内容中的HashMap条目相互引用。 在设计堆分析算法时,我们从来没有想到这是可能的。 显然我们错了。
由于已知HashMap实现不是线程安全的,因此我现在怀疑它与HashMap使用的并发性问题有关。 实际上,在java.util.HashMap的设计中隐藏着一个问题。 如您所知, HashMap由存储区数组组成,每个存储区都引用一个链接的条目列表。 条目依次引用列表中的下一个条目,直到最后一个条目引用null:
我们的分析仪遇到的问题是,两个条目相互引用形成一个封闭的循环。
在Google的帮助下,我发现了最终如何创建这样的循环引用是多线程环境中的一个问题。 再次提醒您, HashMap在运行时会根据映射中的条目数动态调整大小。 默认情况下, HashMaps使用75%的负载率。 这意味着只要地图中的条目数超过可用容量的75%,地图大小就会增加,以避免在地图元素条目上发生太多冲突。
所以我在这里。 显然,有多个线程试图同时调整地图的大小,从而在某些存储桶中创建了一个循环。 罪魁祸首最终隐藏在Java HashMap源代码的以下几行中:
void transfer(Entry[] newTable, boolean rehash) {... skipped for brevity ...Entry next = e.next;if (rehash) {e.hash = null == e.key ? 0 : hash(e.key);}... skipped for brevity ...
}
现在,我们的分析端点解决方案非常简单。 我们只需要保留关于已处理条目的分类帐,而无需对所有条目进行两次处理即可。
我确实相信这可以作为失败抽象的一个很好的例子。 Java中的HashMaps构建良好,即使您不了解实现细节,也可以很好地为您服务。 直到他们不这样做。 在这种情况下,对数据结构实现细节的深入了解将为您带来一切不同。
翻译自: https://www.javacodegeeks.com/2016/08/resizing-hashmap-dangers-ahead.html
hashmap大小