ConcurrentLinkedQueue$Node 在 remove() 后仍保留在堆中

我有一个多线程应用程序编写和读取并发链接队列,它在概念上用于支持列表/表中的条目。我最初使用 ConcurrentHashMap 来实现这一点,效果很好。一项新要求要求跟踪传入的订单条目,因此可以根据某些条件以最旧的第一个顺序删除它们。ConcurrentLinkedQueue似乎是一个不错的选择,从功能上讲,它运行良好。

可配置的条目数量保存在内存中,当达到限制时提供新条目时,将按最旧的优先顺序在队列中搜索可以删除的条目。某些条目不会被系统删除并等待客户端交互。

似乎正在发生的事情是,我在队列的前面有一个条目,比如100K之前。队列似乎具有有限数量的已配置条目(size() == 100),但在分析时,我发现内存中有大约 100K ConcurrentLinkedQueue$Node 对象。这似乎是设计使然,只是浏览了 ConcurrentLinkedQueue 的源代码,删除只是删除了对所存储对象的引用,但保留了链接列表以供迭代。

最后,我的问题是:有没有一种“更好”的懒惰方法来处理这种性质的集合?我喜欢 ConcurrentLinkedQueue 的速度,我只是负担不起在这种情况下似乎可能的无限泄漏。如果没有,似乎我必须创建第二个结构来跟踪顺序,并且可能具有相同的问题,以及同步问题。


答案 1

这里实际发生的是 remove 方法准备一个轮询线程来清空链接的引用。

ConcurrentLinkedQueue 是一个非阻塞线程安全队列实现。但是,当您尝试从队列轮询节点时,它是一个双函数过程。首先,为该值清空,然后为引用清空。CAS 操作是一个单一的原子函数,它不会为轮询提供模拟分辨率。

轮询时发生的情况是,第一个成功的线程将获得节点的值并清空该值,然后该线程将尝试清空引用。然后,另一个线程可能会进入并尝试从队列中轮询。为了确保此队列具有非阻塞属性(即一个线程的失败不会导致另一个线程的失败),新的传入线程将查看该值是否为空,如果该值为空,则该线程将空引用并再次尝试 poll()。

因此,您在这里看到的是,删除线程只是在准备任何新的轮询线程来清空引用。我认为尝试实现非阻塞删除函数几乎是不可能的,因为这需要三个原子函数。该值的 null 引用到所述节点,最后是从该节点父节点到其后续节点的新引用。

回答您的最后一个问题。没有更好的方法来实现删除和维护队列的非阻塞状态。至少在这一点上是这样。一旦处理器开始推出2路和3路外壳,那么这是可能的。


答案 2

队列的主要语义是 add/poll。如果您在 ConcurrentLinkedQueue 上使用 poll() ,它将按预期清理。根据您的描述,poll() 应该可以让您删除最旧的条目。为什么不使用它而不是 remove()


推荐