Узел ConcurrentLinkedQueue $ остается в куче после удаления () - PullRequest
9 голосов
/ 30 марта 2010

У меня есть многопоточное приложение, пишущее и читающее ConcurrentLinkedQueue, которое концептуально используется для поддержки записей в списке / таблице. Первоначально я использовал ConcurrentHashMap для этого, который работал хорошо. Новое требование требовало отслеживания поступивших заказов, поэтому их можно было удалить в старом порядке в зависимости от некоторых условий. ConcurrentLinkedQueue оказался хорошим выбором, и функционально он работает хорошо.

Конфигурируемое количество записей хранится в памяти, и, когда новая запись предлагается, когда достигается предел, очередь ищется в старшем порядке для поиска той, которая может быть удалена. Некоторые записи не должны быть удалены системой и ждут взаимодействия с клиентом.

Похоже, что происходит, у меня есть запись в начале очереди, скажем, 100K записей назад. Похоже, в очереди имеется ограниченное количество настроенных записей (size () == 100), но при профилировании я обнаружил, что в памяти было ~ 100K объектов ConcurrentLinkedQueue $ Node. Похоже, что это сделано специально, просто взглянув на источник для ConcurrentLinkedQueue, удаление просто удаляет ссылку на сохраняемый объект, но оставляет связанный список на месте для итерации.

Наконец, мой вопрос: есть ли «лучший» ленивый способ обработки коллекции такого рода? Мне нравится скорость ConcurrentLinkedQueue, я просто не могу позволить себе неограниченную утечку, которая кажется возможной в этом случае. Если нет, то мне кажется, что мне придется создать вторую структуру для отслеживания порядка и может иметь те же проблемы, а также проблему синхронизации.

Ответы [ 3 ]

9 голосов
/ 30 марта 2010

То, что на самом деле происходит здесь, это метод remove, который подготавливает поток опроса для обнуления связанной ссылки.

ConcurrentLinkedQueue является неблокирующей потокобезопасной реализацией очереди. Однако, когда вы пытаетесь опросить узел из очереди, это процесс с двумя функциями. Сначала вы обнуляете значение, а затем обнуляете ссылку. Операция CAS - это отдельная элементарная функция, которая не обеспечивает непосредственного разрешения для опроса.

Что происходит при опросе, так это то, что первый успешный поток получит значение узла и обнулит это значение, этот поток затем попытается обнулить ссылку. Вполне возможно, что затем придет другой поток и попытается опросить из очереди. Чтобы убедиться, что в этой очереди хранится неблокирующее свойство (то есть, сбой одного потока не приведет к отказу другого потока), этот новый входящий поток увидит, имеет ли значение значение NULL, если он равен NULL, этот поток обнулит ссылку и попытается снова опросить ().

Итак, вы видите, что поток удаления просто подготавливает любой новый поток опроса к нулевой ссылке. Попытка достичь неблокирующей функции удаления, я думаю, почти невозможна, потому что для этого потребуются три атомарные функции. Нулевое значение, значение NULL, ссылающееся на указанный узел, и, наконец, новая ссылка от родительского узла к его преемнику.

Чтобы ответить на ваш последний вопрос. Не существует лучшего способа реализовать удаление и поддержание неблокирующего состояния очереди. Это по крайней мере на данный момент. Как только процессоры начнут выходить с 2-х и 3-х сторонним корпусом, это станет возможным.

1 голос
/ 09 января 2012

Глядя на исходный код для 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);
}
1 голос
/ 30 марта 2010

Основная семантика очереди - add / poll. Если вы используете poll () на ConcurrentLinkedQueue , он будет очищен как следует. Исходя из вашего описания, poll () должен дать вам удалить самую старую запись. Почему бы не использовать его вместо remove () ?

...