我用过 LinkedHashMapaccessOrder true 并同时允许最多 500 个条目作为数据的 LRU 缓存。但由于可扩展性问题,我想转向一些线程安全的替代方案。 ConcurrentHashMap 在这方面似乎不错,但缺乏以下功能 accessOrderremoveEldestEntry(Map.Entry e) 在发现 LinkedHashMap. 。任何人都可以指出一些链接或帮助我简化实施。

有帮助吗?

解决方案

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

就您而言,由于您正在执行 FIFO,因此您可以保留一个单独的 并发链接队列. 。当您将一个对象添加到 ConcurrentHashMap 时,请对该对象执行 ConcurrentLinkedQueue.add() 操作。当您想要逐出一个条目时,请执行 ConcurrentLinkedQueue.poll() 来删除最旧的对象,然后将其也从 ConcurrentHashMap 中删除。

更新:该领域的其他可能性包括 Java 集合 同步包装器 和Java 1.6 并发跳表映射.

其他提示

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

现在看起来似乎已经老了,但至少只是为了我自己的历史跟踪,我将在这里添加我的解决方案:我结合了映射K - <!> gt的WeurrentHashMap; WeakReference的子类,ConcurrentLinkedQueue和一个接口定义基于K的值对象的反序列化以正确运行LRU缓存。队列中包含强引用,GC将在适当时从内存中逐出值。跟踪AtomicInteger所涉及的队列大小,因为您无法真正检查队列以确定何时驱逐。缓存将处理驱逐/添加到队列以及地图管理。如果GC从内存中清除了值,则反序列化接口的实现将处理检索值。我还有另一个实现,涉及假脱机到磁盘/重新读取被假脱机的内容,但这比我在这里发布的解决方案要慢得多,因为我要同步假脱机/读取。

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

LRU缓存至少需要了解总体结构。他们需要像成员的数量或成员的大小来决定何时驱逐,然后他们需要能够协调驱逐与并发尝试读取,添加或删除元素。尝试减少并发访问<!>“main <!>”所需的同步;结构与你的驱逐机制作斗争,并迫使你的驱逐政策在其保障中不那么精确。

当你想要驱逐一个条目<!>时,当前接受的答案会提到<!>“。这就是摩擦。你怎么知道什么时候想要逐出一个条目?您需要暂停哪些其他操作才能做出此决定?

将地图包裹在Collections.synchronizedMap()中。如果您需要调用其他方法,那么synchronize在地图上从此调用中返回,并在原始地图上调用原始方法(请参阅javadocs以获取示例)。迭代键等时也是如此。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top