Как реализовать ConcurrentHashMap с функциями, аналогичными LinkedHashMap? - PullRequest
14 голосов
/ 29 ноября 2009

Я использовал LinkedHashMap с accessOrder true наряду с разрешением максимум 500 записей в любое время в качестве кэша LRU для данных. Но из-за проблем с масштабируемостью я хочу перейти к некоторой поточно-ориентированной альтернативе. ConcurrentHashMap кажется хорошим в этом отношении, но ему не хватает функций accessOrder и removeEldestEntry(Map.Entry e), имеющихся в LinkedHashMap. Может кто-нибудь указать на какую-то ссылку или помочь мне облегчить реализацию.

Ответы [ 5 ]

10 голосов
/ 29 ноября 2009

Недавно я сделал нечто подобное с ConcurrentHashMap<String,CacheEntry>, где CacheEntry оборачивает фактический элемент и добавляет статистику удаления кэша: время истечения, время вставки (для выселения FIFO / LIFO), время последнего использования (для выселения LRU / MRU), число попаданий (для выселения LFU / MFU) и т. д. Фактическое выселение синхронизируется и создает ArrayList<CacheEntry> и выполняет для него Collections.sort (), используя соответствующий компаратор для стратегии выселения. Так как это дорого, каждое выселение сбрасывает нижние 5% CacheEntries. Я уверен, что настройка производительности поможет, хотя.

В вашем случае, поскольку вы выполняете FIFO, вы можете оставить отдельный ConcurrentLinkedQueue . Когда вы добавляете объект в ConcurrentHashMap, выполняйте ConcurrentLinkedQueue.add () этого объекта. Если вы хотите удалить запись, выполните ConcurrentLinkedQueue.poll (), чтобы удалить самый старый объект, а затем удалите его из ConcurrentHashMap.

Обновление: другие возможности в этой области включают Java Collections оболочку синхронизации и Java 1.6 ConcurrentSkipListMap .

2 голосов
/ 03 октября 2011

Это может показаться старым, но, по крайней мере, только для моего собственного отслеживания истории, я собираюсь добавить свое решение здесь: я объединил ConcurrentHashMap, который отображает K-> подкласс WeakReference, ConcurrentLinkedQueue, и интерфейс, который определяет десериализацию значения объектов на основе K для правильного запуска LRU-кэширования. Очередь содержит строгие ссылки, и GC выселяет значения из памяти, когда это необходимо. Отслеживание размера очереди связано с AtomicInteger, так как вы не можете реально проверить очередь, чтобы определить, когда выселить. Кеш будет обрабатывать выселение из / добавление в очередь, а также управление картой. Если GC вытеснил значение из памяти, реализация интерфейса десериализации будет обрабатывать получение значения обратно. У меня также была другая реализация, которая включала в себя буферизацию на диск / повторное чтение буферизованного, но это было намного медленнее, чем решение, которое я выложил здесь, так как мне пришлось синхронизировать буферизацию / чтение.

2 голосов
/ 29 ноября 2009

Вы пытались использовать одно из многих решений для кэширования, таких как ehcache? Вы можете попробовать использовать LinkedHashMap с ReadWriteLock. Это даст вам одновременный доступ для чтения.

0 голосов
/ 30 ноября 2009

Вы упоминаете о желании решить проблемы масштабируемости с помощью «поточно-ориентированной» альтернативы. «Потокобезопасность» здесь означает, что структура допускает попыток одновременного доступа, поскольку она не будет повреждена при одновременном использовании без внешней синхронизации. Однако такая терпимость не обязательно помогает улучшить «масштабируемость». В самом простом, хотя обычно ошибочном, подходе вы попытаетесь внутренне синхронизировать свою структуру и все же оставить неатомарные операции check-then-act небезопасными.

Кэши LRU требуют, по крайней мере, некоторой осведомленности об общей структуре. Им нужно что-то вроде количества членов или размера членов, чтобы решить, когда выселять, а затем они должны быть в состоянии скоординировать выселение с одновременными попытками чтения, добавления или удаления элементов. Попытка уменьшить синхронизацию, необходимую для одновременного доступа к «основной» структуре, противоречит вашему механизму выселения и вынуждает вашу политику выселения быть менее точной в своих гарантиях.

В текущем принятом ответе упоминается "когда вы хотите выселить запись". В этом и заключается загвоздка. Как вы знаете, когда вы хотите выселить запись? Какие еще операции вам нужно приостановить, чтобы принять это решение?

0 голосов
/ 29 ноября 2009

Оберните карту в Collections.synchronizedMap(). Если вам нужно вызвать дополнительные методы, тогда synchronize на карте, которую вы вернули после этого вызова, и вызовите оригинальный метод на исходной карте ( см. Пример javadoc ). То же самое применимо, когда вы перебираете ключи и т. Д.

...