AbstractConcurrentMap是Groovy的核心类,用于存储在运行时添加到Groovy类的动态属性。我在Groovy 1.8.8中使用了Grails 2.1.2,但是我认为所有Groovy版本中都存在此问题(链接的源代码适用于Groovy 2.4.3版)。
该问题发生在内部类Segment的put()方法中(第105行):
当当前计数更大时,则地图的阈值发生rehash()。现在最棘手的部分是,Map包含对对象的软引用,并rehash()验证了这些引用。因此,当GC丢弃软引用时,结果段将不会扩展(如put()方法中所假定的)。
last line of rehash()中的Segmen't内部计数器中的值更新为count = newCount
(这是“有效”未丢弃引用的数量,并且可以小于如上所述的先前计数)
在完成rehash()之后,put()方法继续,但是有问题的部分是,它忽略了内部count
的先前设置,并且在每种情况下都在124行中设置了先前的count + 1值, 143和159
因此,正在执行以下步骤:
地图状态:threshold = 786432; count=786432
新元素插入到地图中:count = 786433; threshold = 786432
因为新计数将大于阈值rehash()
rehash()发现,大多数对象都是垃圾回收,因此它不会增加Segment的大小,但是无论如何,它将所有对象从一个表复制到另一个表(System.arrayCopy())。
rehash()将内部计数设置为较小的新值,因为许多对象已被垃圾回收(软引用),所以说:count = 486 000
put()继续,忽略count = 486 000
并将计数设置为count = 786433
插入了另一个元素,但是在此状态下,计数仍然大于阈值,因此再次发生了哈希
从现在开始,添加到地图的每个元素都会触发rehash(),这会对性能产生巨大影响。
在多线程环境中发生这种情况时,所有其他线程都在等待(停放)lock(),直到完成rehash()和put()(然后下一个线程再次进行rehash())。您可以想象这会对性能产生什么影响...
我不明白,尽管该类被广泛使用,但该错误如何能够在许多版本中幸存下来却没有人注意到。也许我想念什么?
建议的解决方案:
重新哈希完成后更新c变量。
在第105和106行之间添加:
c = count + 1
最佳答案
该错误已在Groovy JIRA https://issues.apache.org/jira/browse/GROOVY-7448上报告,现已修复。
Fix Version/s:
2.4.4, 2.5.0-beta-1