代码之家  ›  专栏  ›  技术社区  ›  Steve Jackson

concurrentLinkedQueue$节点在删除()后仍保留在堆中

  •  9
  • Steve Jackson  · 技术社区  · 15 年前

    我有一个多线程应用程序,它编写和读取一个ConcurrentLinkedQueue,在概念上用于返回列表/表中的条目。我最初用的是一个Concurrenthashmap,效果很好。一项新的要求是跟踪订单条目,以便根据某些条件,以最早的第一顺序删除它们。ConcurrentLinkedQueue似乎是一个不错的选择,而且在功能上也很好。

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

    似乎发生的是我在队列前面有一个条目,比如说10万个条目。队列中配置的条目数似乎有限(size()=100),但在进行分析时,我发现内存中有~10万个ConcurrentLinkedQueue$节点对象。这似乎是由设计造成的,只需浏览一下ConcurrentLinkedQueue的源,移除只会删除对正在存储的对象的引用,但会将链接列表保留在适当的位置进行迭代。

    最后,我的问题是:是否有一种“更好的”懒惰的方式来处理这种性质的集合?我喜欢当前链接队列的速度,我只是承受不起这种情况下可能出现的无限泄漏。如果不是这样,我似乎需要创建第二个结构来跟踪顺序,并且可能有相同的问题,另外还有一个同步问题。

    3 回复  |  直到 9 年前
        1
  •  9
  •   John Vint    15 年前

    这里实际发生的是remove方法准备了一个轮询线程,以使链接引用为空。

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

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

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

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

        2
  •  1
  •   Slava Imeshev    15 年前

    队列的主要语义是add/poll。如果你使用 污染() ConcurrentLinkedQueue ,它将按应该的方式进行清洁。根据你的描述, 污染() 应该给你删除最旧的条目。为什么不用它代替 移除() ?

        3
  •  1
  •   Amir Hadadi    9 年前

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

    p = p.getNext();
    

    现在代码是:

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

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

    事实上,当我尝试以下方法时,我会得到旧版本的OOME,而不是新版本的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);
    }