0

我目前正在运行一个高度并发的基准测试,它ConcurrentSkipList从 Java 集合中访问 a 。我发现线程在该方法中被阻塞,更准确地说是:

java.util.concurrent.ConcurrentSkipListMap.doGet(ConcurrentSkipListMap.java:828)    
java.util.concurrent.ConcurrentSkipListMap.get(ConcurrentSkipListMap.java:1626)

(这是通过在 10 秒的时间间隔内打印每个单独线程的堆栈跟踪来获得的)。几分钟后仍未解决此问题

这是集合的预期行为吗?哪些并发的其他集合可能会遇到阻塞?

经过测试,我表现出与ConcurrentHashMaps 类似的行为:

java.util.concurrent.ConcurrentHashMap.get(ConcurrentHashMap.java:994)
4

3 回答 3

5

这很可能是一个虚假的结果。

当您要求 Java 转储其所有当前堆栈跟踪时,它会告诉每个线程在到达屈服点时等待,然后捕获跟踪,然后恢复所有线程。正如您可以想象的那样,这意味着屈服点在这些轨迹中被过度表示;其中包括同步方法、volatile访问等ConcurrentSkipListMap.head,一个volatile字段,在 中被访问doGet

有关更详细的分析,请参阅本文。

Solaris Studio 有一个分析器,它从操作系统捕获堆栈跟踪并将它们转换为 Java 堆栈跟踪。这消除了对屈服点的偏见,并为您提供更准确的结果;你可能会发现这doGet几乎完全消失了。我只是幸运地在 Linux 上运行了它,即便如此它也不是开箱即用的。如果您有兴趣,请在评论中询问我如何设置它,我很乐意提供帮助。

作为一种更简单的方法,您可以将调用包装起来ConcurrentSkipList.getSystem.nanoTime()以检查这是否真的是您的时间去的地方。弄清楚您在该方法上花费了多少时间,并确认它是否符合您的预期,因为分析器说您在该方法上花费了如此多的时间。

无耻的自我插入:几个月前,我创建了一个简单的程序来演示这一点,用于工作演示。如果您对探查器运行它,它应该会SpinWork.work显示很多,而HardWork.work根本不显示 - 尽管后者实际上需要更多时间。它不包含屈服点。

于 2013-10-28T15:04:27.550 回答
0

好吧,它并没有以最真实的形式阻塞。阻塞意味着线程活动的暂停。ConcurrentSkipListMap 是非阻塞的,它会一直旋转直到成功。但它也保证它最终会成功(即它不应该进入无限循环)

话虽这么说,除非您异步执行许多获取和许多放置操作,否则我看不出您如何在这种方法上花费这么多时间。

如果您可以通过示例重新创建它并与我们分享可能会有所帮助。

于 2013-10-28T15:02:00.663 回答
0

ConcurrentHashMap.get是易失性读取,这意味着 CPU 必须先完成所有未完成的写入,然后才能执行读取。这称为存储/加载屏障。根据其他线程/内核中发生的情况,这可能需要很长时间。请参阅https://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html

于 2013-10-28T15:05:59.323 回答