Безопасно ли повторно вставлять запись из Guava RemovalListener? - PullRequest
13 голосов
/ 07 января 2012

У меня есть Guava Cache (или, скорее, я перехожу с MapMaker на Cache), и значения представляют долгосрочные задания. Я хотел бы добавить поведение expireAfterAccess в кеш, так как это лучший способ его очистить; тем не менее, задание может все еще выполняться, хотя к нему некоторое время не обращались через кеш, и в этом случае мне нужно предотвратить его удаление из кеша. У меня три вопроса:

  1. Безопасно ли повторно вставлять запись кэша, которая удаляется во время обратного вызова RemovalListener?

  2. Если так, это потокобезопасно, так что нет никакого возможного способа, которым CacheLoader мог бы создать второе значение для этого ключа, в то время как обратный вызов RemovalListener все еще происходит в другом потоке?

  3. Есть ли лучший способ добиться того, чего я хочу? Это не строго / только «кеш» - важно, чтобы для каждого ключа использовалось одно и только одно значение - но я также хочу кэшировать запись в течение некоторого времени после завершения задания, которое оно представляет. Раньше я использовал MapMaker, и в этом классе поведение, которое мне сейчас нужно, устарело. Регулярное пингование карты во время выполнения заданий не элегантно, а в моем случае невозможно. Возможно, правильное решение состоит в том, чтобы иметь две карты, одну без выселения и одну с, и переносить их по мере их заполнения.

Я также сделаю запрос функции - это решит проблему: разрешить блокировку отдельных записей для предотвращения выселения (а затем разблокировки).

[Изменить, чтобы добавить некоторые детали]: Ключи на этой карте относятся к файлам данных. Значения: либо текущее задание на запись, либо завершенное задание на запись, либо - если задание не выполняется - доступный только для чтения, созданный при поиске объект с информацией, считанной из файла. Важно, чтобы для каждого файла было ровно ноль или одна запись. Я мог бы использовать отдельные карты для этих двух вещей, но для каждого ключа должна быть координация, чтобы быть уверенным, что только одна или другая существует одновременно. Использование одной карты упрощает ее с точки зрения обеспечения правильности параллелизма.

Ответы [ 4 ]

2 голосов
/ 19 января 2012

Я посмотрел код Guava и обнаружил, что CustomConcurrentHashMap (который CacheBuilder использует в качестве базовой реализации) добавляет уведомления об удалении во внутреннюю очередь и обрабатывает их позже . Следовательно, повторная вставка записи в карту из обратного вызова удаления технически «безопасна», но открывает окно, в течение которого запись отсутствует на карте, и, следовательно, альтернативная запись может быть создана другим потоком через CacheLoader.

Я решил эту проблему, используя предложение @Louis в своем комментарии выше. Основная карта вообще не имеет срока действия, но каждый раз, когда я что-то ищу на этой карте, я также добавляю запись во вторичный кэш, который имеет expireAfterAccess. В приемнике удаления для этого вторичного кэша я принимаю некоторые решения относительно того, следует ли удалить запись из первичной карты. Ничто другое не использует вторичный кеш. Кажется, это элегантное решение для условного выселения, и оно работает. (Я действительно все еще использую Guava r09 MapMaker для этого решения, но он должен одинаково хорошо работать для Guava 11.)

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

Я не совсем уверен в точной проблеме, но другое решение будет иметь кэш с softValues() вместо максимального размера или времени истечения. Каждый раз, когда вы получаете доступ к значению кэша (в вашем примере, запускаете вычисления), вы должны поддерживать состояние где-то еще с сильной ссылкой на это значение. Это предотвратит GCed-значение. Всякий раз, когда использование этого значения падает до нуля (в вашем примере вычисление заканчивается и все в порядке, чтобы значение исчезло), вы можете удалить все сильные ссылки. Например, вы можете использовать AtomicLongMap со значением Cache в качестве ключа AtomicLongMap и периодически вызывать removeAllZeros() на карте.

Обратите внимание, что, как утверждает Javadoc, использование softValues() сопряжено с компромиссами.

0 голосов
/ 01 августа 2016

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

  1. Безопасно ли повторно вставлять запись кэша, которая удаляется во время обратного вызова RemovalListener?

Да, это безопасно;вызов .put() изнутри RemovalListener никак не нарушит кеш.Вообще говоря, у Cache нет проблем с одновременными мутациями .

Если это так, это потокобезопасно, так что нет никакого способа, которым CacheLoader мог бы создать второе значение для этого ключа, пока обратный вызов RemovalListener все еще происходит в другом потоке?

Нетмутации, вызванные RemovalListener, не являются атомарными в отношении ассоциированного удаления.Мало того, что другой поток может получить доступ к кешу между завершением удаления и получателем уведомлений, возможно, что между этими двумя действиями существует значительная задержка.Как в вики упоминается " операции прослушивания удаления выполняются синхронно [во время] обслуживания кеша ". продолжает , чтобы сказать, что задачи обслуживания выполняются небольшими партиями, и не предоставляется никаких гарантий относительно того, когда (или неявно, если) они действительно будут выполняться.

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

Как вы говорите, на самом деле это не кеш (или, точнее, кеш удаления).Если есть объекты, которые вы не хотите выселять, просто не храните их в кэше для выселения.Как Луи предлагает использовать настройку с двумя структурами данных с картой для текущих процессов, а удаленный кеш для завершенных процессов будет делать именно то, что вам нужно.

0 голосов
/ 03 марта 2012

Вчера мне пришло в голову, что я обновился до Guava 11, что - это способ блокировки записей в кеше: используйте maximumWeight в качестве единственного метода удаления, и ваш весовщик возвращает вес 0 для записей, которые должны оставаться заблокированными в кеше. Я не проверял это путем тестирования, но документация для CacheBuilder.weigher () состояний,

"Если вес записи равен нулю, она не будет учитываться для выселения на основе размера (хотя она все еще может быть удалена другими средствами)."

...