Подтвердить что ты не робот

Можно ли повторно вставить запись из Guava RemovalListener?

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

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

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

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

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

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

4b9b3361

Ответ 1

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

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

Ответ 2

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

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

Ответ 3

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

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

Ответ 4

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

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

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

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

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

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

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