Я использовал LinkedHashMap
с accessOrder
true вместе с разрешением не более 500 записей в любое время в качестве кэша LRU для данных. Но из-за проблем с масштабируемостью я хочу перейти к какой-то альтернативной потоку. ConcurrentHashMap
кажется хорошим в этом отношении, но не имеет особенностей accessOrder
и removeEldestEntry(Map.Entry e)
, найденных в LinkedHashMap
. Может ли кто-нибудь указать на какую-то ссылку или помочь мне облегчить ее реализацию.
Как реализовать ConcurrentHashMap с функциями, подобными в LinkedHashMap?
Ответ 1
Недавно я сделал что-то подобное с ConcurrentHashMap<String,CacheEntry>
, где CacheEntry обертывает фактический элемент и добавляет статистику выселения кеша: время истечения, время вставки (для выселения FIFO/LIFO), последнее время использования (для выселения LRU/MRU), номер хитов (для выселения LFU/MFU) и т.д. Фактическое выселение синхронизируется и создает ArrayList<CacheEntry>
, а для него используется Collections.sort(), используя соответствующий Comparator для стратегии выселения. Так как это дорого, каждое выселение затем удаляет нижние 5% CacheEntries. Я уверен, что настройка производительности поможет.
В вашем случае, поскольку вы выполняете FIFO, вы можете сохранить отдельный ConcurrentLinkedQueue. Когда вы добавляете объект в ConcurrentHashMap, выполните ConcurrentLinkedQueue.add() этого объекта. Когда вы хотите выселить запись, выполните ConcurrentLinkedQueue.poll(), чтобы удалить самый старый объект, а затем удалите его из ConcurrentHashMap.
Обновление. Другие возможности в этой области включают сборку Java Collections и Java 1.6 ConcurrentSkipListMap.
Ответ 2
Ответ 3
Вы пытались использовать одно из многих решений для кеширования, таких как ehcache? Вы можете попробовать использовать LinkedHashMap с помощью ReadWriteLock. Это обеспечит вам параллельный доступ для чтения.
Ответ 4
Теперь это может показаться старым, но, по крайней мере, для моего собственного отслеживания истории, я собираюсь добавить мое решение здесь: я объединил ConcurrentHashMap, который отображает K- > подкласс WeakReference, ConcurrentLinkedQueue и интерфейс, который определяет десерилизацию объекты ценности, основанные на K, для правильного выполнения кеширования LRU. Очередь содержит сильные ссылки, и GC, если необходимо, выцифровывает значения из памяти. Отслеживание размера очереди включало AtomicInteger, так как вы не можете проверить очередь, чтобы определить, когда выселять. Кэш будет обрабатывать выселение из/добавление в очередь, а также управление картой. Если GC вытеснил значение из памяти, реализация интерфейса десериализации будет обрабатывать возврат значения обратно. У меня также была другая реализация, которая включала буферизацию на диск/повторное чтение того, что было намотано, но это было намного медленнее, чем предлагаемое мной решение, поскольку Ihad синхронизирует буферизацию/чтение.
Ответ 5
Вы упомянули о необходимости решить проблемы масштабируемости с помощью "поточно-безопасной" альтернативы. "Безопасность потоков" здесь означает, что структура терпима к попыткам одновременного доступа, поскольку она не будет терпеть коррупцию при одновременном использовании без внешней синхронизации. Однако такая толерантность не обязательно помогает улучшить "масштабируемость". В простейшем, хотя обычно ошибочном, подходе вы попытаетесь синхронизировать свою структуру внутри себя и по-прежнему оставляете неатомные операции check-then-act небезопасными.
В кэшах LRU требуется, по крайней мере, некоторое понимание общей структуры. Им нужно что-то вроде подсчета членов или размера членов, чтобы решить, когда выселять, а затем они должны иметь возможность координировать выселение с одновременными попытками читать, добавлять или удалять элементы. Попытка сократить синхронизацию, необходимую для одновременного доступа к "основной" структуре, борется с вашим механизмом выселения и заставляет вашу политику выселения быть менее точной в своих гарантиях.
В принятом в настоящее время ответе упоминается "когда вы хотите выселить запись". В этом и заключается руб. Откуда вы знаете, когда хотите выселить запись? Какие другие операции вам нужно сделать, чтобы сделать это решение?
Ответ 6
В тот момент, когда вы используете другую структуру данных вместе с concurrenthashmap, атомарность операций, таких как добавление нового элемента в concurrenthashmap и добавление в другую структуру данных, не может быть гарантирована без дополнительной синхронизации, такой как ReadWriteLock, которая снизит производительность
Ответ 7
Оберните карту в Collections.synchronizedMap()
. Если вам нужно вызвать дополнительные методы, тогда synchronize
на карте, полученной вами от этого вызова, и вызовите исходный метод на исходной карте (см. javadocs для примера). То же самое происходит, когда вы перебираете ключи и т.д.