删除后,ConcurrentLinkedQueue $ Node仍保留在堆中

时间:2010-03-30 17:17:07

标签: java concurrency heap java.util.concurrent

我有一个多线程应用程序编写和读取ConcurrentLinkedQueue,它在概念上用于支持列表/表中的条目。我最初为此使用了ConcurrentHashMap,效果很好。需要跟踪订单条目的新要求,因此可以在最早的第一个订单中删除它们,具体取决于某些条件。 ConcurrentLinkedQueue似乎是一个不错的选择,功能上它运作良好。

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

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

最后我的问题:是否有一种“更好”的懒惰方式来处理这种性质的集合?我喜欢ConcurrentLinkedQueue的速度,我无法承受在这种情况下似乎可能出现的无界泄漏。如果没有,似乎我必须创建第二个结构来跟踪订单,可能会遇到同样的问题,加上同步问题。

3 个答案:

答案 0 :(得分:9)

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

ConcurrentLinkedQueue是一个非阻塞线程安全的Queue实现。但是,当您尝试从队列中轮询节点时,它是一个双功能过程。首先,您将该值置零,然后使引用为空。 CAS操作是单个原子函数,不能为轮询提供免费解析。

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

所以你在这里看到的是删除线程只是准备任何新的轮询线程来使引用为空。试图实现非阻塞删除功能我认为几乎是不可能的,因为这将需要三个原子功能。 null引用所述节点的null值,以及从该节点父节点到其后继节点的新引用。

回答你的上一个问题。没有更好的方法来实现删除和维护队列的非阻塞状态是不可能的。至少在这一点上。一旦处理器开始使用2路和3路套管,那么这是可能的。

答案 1 :(得分:1)

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

答案 2 :(得分:1)

查看1.6.0_29的源代码,似乎修改了CLQ的迭代器以尝试删除具有空项的节点。而不是:

p = p.getNext();

代码现在是:

Node<E> next = succ(p);
if (pred != null && next != null)
    pred.casNext(p, next);
p = next; 

这是作为错误修复程序的一部分添加的:http://bugs.sun.com/view_bug.do?bug_id=6785442

确实,当我尝试以下操作时,我会得到一个旧版本的OOME,但不会使用新版本:

Queue<Integer> queue = new ConcurrentLinkedQueue<Integer>();
for (int i=0; i<10000; i++)
{
    for (int j=0; j<100000; j++)
    {
        queue.add(j);
    }
    boolean start = true;
    for (Iterator<Integer> iter = queue.iterator(); iter.hasNext(); )
    {
        iter.next();
        if (!start)
            iter.remove();
        start = false;
    }
    System.out.println(i);
}