阻塞行为-并发数据结构Java

阻塞行为-并发数据结构Java,java,multithreading,collections,concurrency,synchronization,Java,Multithreading,Collections,Concurrency,Synchronization,我目前正在运行一个高度并发的基准测试,它从Java集合访问一个ConcurrentSkipList。我发现线程在该方法中被阻塞,更准确地说是在这里: java.util.concurrent.ConcurrentSkipListMap.doGet(ConcurrentSkipListMap.java:828) java.util.concurrent.ConcurrentSkipListMap.get(ConcurrentSkipListMap.java:1626) (这是通过每隔10

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

java.util.concurrent.ConcurrentSkipListMap.doGet(ConcurrentSkipListMap.java:828)    
java.util.concurrent.ConcurrentSkipListMap.get(ConcurrentSkipListMap.java:1626)
(这是通过每隔10秒打印每个线程的堆栈跟踪来实现的)。几分钟后仍未解决此问题

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

经过测试,我在
ConcurrentHashMap
s上表现出类似的行为:

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

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

也就是说,除非你异步地做很多很多get和很多put,否则我不明白你怎么会在这个方法中花费这么多时间


如果你能用一个例子重新创建它,并与我们分享,这可能会有所帮助。

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

也就是说,除非你异步地做很多很多get和很多put,否则我不明白你怎么会在这个方法中花费这么多时间


如果你能用一个例子重新创建它,并与我们分享,这可能会有所帮助。

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

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

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

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

作为一种更简单的方法,您可以使用
System.nanoTime()
将对
ConcurrentSkipList.get
的调用包装起来,以检查这是否真的是您的时间。计算出你在该方法中花费了多少时间,并确认它是否与你所期望的有关,因为探查器说你在该方法中花费了如此之多的时间


无耻的自我插入:几个月前,我为工作中的一次演示创建了一个插件。如果您在探查器上运行它,它应该会显示
SpinWork.work
经常出现,而
HardWork.work
根本不会出现,即使后者实际上需要更多的时间。它不包含屈服点。

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

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

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

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

作为一种更简单的方法,您可以使用
System.nanoTime()
将对
ConcurrentSkipList.get
的调用包装起来,以检查这是否真的是您的时间。计算出你在该方法中花费了多少时间,并确认它是否与你所期望的有关,因为探查器说你在该方法中花费了如此之多的时间


无耻的自我插入:几个月前,我为工作中的一次演示创建了一个插件。如果您在探查器上运行它,它应该会显示
SpinWork.work
经常出现,而
HardWork.work
根本不会出现,即使后者实际上需要更多的时间。它不包含屈服点。

ConcurrentHashMap.get是一种易失性读取,这意味着CPU必须完成所有未完成的写入,然后才能执行读取。这称为存储/装载屏障。这可能需要很长时间,具体取决于其他线程/内核中的运行量。请参阅。

ConcurrentHashMap.get
是一种易失性读取,这意味着CPU必须完成所有未完成的写入,然后才能执行读取。这称为存储/装载屏障。这可能需要很长时间,具体取决于其他线程/内核中的运行量。请参阅。

与我们共享您的代码“阻塞”是什么意思?
ConcurentSkipListMap
是一个非阻塞数据结构,因此您永远不会看到它被阻塞,因为它正在等待锁定。它确实使用,所以如果在基准测试中存在高争用性,它可能只是由于重复失败而被卡住。多大