18

我使用LinkedHashMapaccessOrdertrue 以及在任何时候最多允许 500 个条目作为数据的 LRU 缓存。但是由于可扩展性问题,我想继续使用一些线程安全的替代方案。在这方面看起来不错,ConcurrentHashMap但缺少. 谁能指出一些链接或帮助我简化实施。accessOrderremoveEldestEntry(Map.Entry e)LinkedHashMap

4

6 回答 6

12

我最近做了类似的事情ConcurrentHashMap<String,CacheEntry>,其中​​ CacheEntry 包装了实际项目并添加了缓存逐出统计信息:过期时间、插入时间(对于 FIFO/LIFO 逐出)、上次使用时间(对于 LRU/MRU 逐出)、命中数(对于 LFU/ MFU eviction) 等。实际的驱逐是同步的ArrayList<CacheEntry>,并使用适当的 Comparator 为驱逐策略创建一个并在其上执行 Collections.sort()。由于这很昂贵,因此每次驱逐都会减少 CacheEntries 的底部 5%。我确信性能调整会有所帮助。

在您的情况下,由于您正在执行 FIFO,因此您可以保留一个单独的ConcurrentLinkedQueue。当您将对象添加到 ConcurrentHashMap 时,请对该对象执行 ConcurrentLinkedQueue.add()。当您想要驱逐一个条目时,执行 ConcurrentLinkedQueue.poll() 以删除最旧的对象,然后将其从 ConcurrentHashMap 中删除。

更新:这方面的其他可能性包括 Java Collections同步包装器和 Java 1.6 ConcurrentSkipListMap

于 2009-11-29T15:21:40.173 回答
2

您是否尝试过使用 ehcache 等众多缓存解决方案之一?您可以尝试将 LinkedHashMap 与 ReadWriteLock 一起使用。这将为您提供并发读取访问权限。

于 2009-11-29T16:35:29.140 回答
2

这现在可能看起来很老了,但至少只是为了我自己的历史跟踪,我将在这里添加我的解决方案:我结合了映射 K-> WeakReference 的子类的 ConcurrentHashMap、ConcurrentLinkedQueue 和一个定义值对象反序列化的接口基于 K 正确运行 LRU 缓存。队列持有强引用,GC 会在适当的时候从内存中逐出这些值。跟踪队列大小涉及 AtomicInteger,因为您无法真正检查队列以确定何时驱逐。缓存将处理从/添加到队列的逐出以及地图管理。如果 GC 从内存中逐出该值,则反序列化接口的实现将处理取回该值。我还有另一个实现涉及到磁盘/重新读取假脱机的内容,

于 2011-10-03T03:15:23.013 回答
0

您提到希望使用“线程安全”替代方案来解决可伸缩性问题。这里的“线程安全”意味着该结构可以容忍并发访问的尝试,因为它不会在没有外部同步的情况下因并发使用而受到破坏。然而,这种容忍度并不一定有助于提高“可扩展性”。在最简单的(尽管通常是被误导的)方法中,您将尝试在内部同步您的结构,但仍然使非原子的 check-then-act操作不安全。

LRU 缓存至少需要对整体结构有所了解。他们需要诸如成员计数或成员大小之类的东西来决定何时驱逐,然后他们需要能够协调驱逐与同时尝试读取、添加或删除元素的尝试。试图减少并发访问“主”结构所需的同步与您的驱逐机制作斗争,并迫使您的驱逐策略在其保证方面不那么精确。

当前接受的答案提到“当您想要驱逐条目时”。问题就在于此。您如何知道何时要驱逐条目?您需要暂停哪些其他操作才能做出此决定?

于 2009-11-30T00:35:42.493 回答
0

当您使用另一个数据结构和 concurrenthashmap 时,如果没有额外的同步(例如 ReadWriteLock),将无法保证操作的原子性,例如在 concurrenthashmap 中添加新项目和添加其他数据结构,这会降低性能

于 2019-08-07T09:37:58.063 回答
-2

将地图包裹在Collections.synchronizedMap(). 如果您需要调用其他方法,则synchronize在您从此调用返回的地图上,并在原始地图上调用原始方法(请参阅 javadocs 以获取示例)。当您迭代键等时,这同样适用。

于 2009-11-29T14:26:15.687 回答