我曾使用 accessOrder
设置为 true 的 LinkedHashMap
,同时允许最多存储 500 条数据作为 LRU 缓存。但由于可扩展性问题,我想转向一些线程安全的替代方案。在这方面,ConcurrentHashMap
看起来不错,但缺少 LinkedHashMap
中的 accessOrder
和 removeEldestEntry(Map.Entry e)
功能。请问有人能提供一些链接或帮助我简化实现吗?
我曾使用 accessOrder
设置为 true 的 LinkedHashMap
,同时允许最多存储 500 条数据作为 LRU 缓存。但由于可扩展性问题,我想转向一些线程安全的替代方案。在这方面,ConcurrentHashMap
看起来不错,但缺少 LinkedHashMap
中的 accessOrder
和 removeEldestEntry(Map.Entry e)
功能。请问有人能提供一些链接或帮助我简化实现吗?
ConcurrentHashMap<String,CacheEntry>
来实现类似的功能,其中CacheEntry包装了实际的项目并添加了缓存逐出统计信息:过期时间、插入时间(用于FIFO/LIFO逐出)、上次使用时间(用于LRU/MRU逐出)、命中次数(用于LFU/MFU逐出)等。实际的逐出是同步的,并创建了一个ArrayList<CacheEntry>
,然后对其使用适当的比较器进行Collections.sort()。由于这很耗时,每个逐出操作都会删除CacheEntries底部的5%。我相信性能调整会有所帮助。你是否尝试过使用像ehcache这样的缓存解决方案? 你可以尝试使用带有ReadWriteLock的LinkedHashMap。这将为您提供并发读访问。
这可能看起来有点老了,但至少为了我的历史记录,我要在这里添加我的解决方案:我将 ConcurrentHashMap(将 K 映射到 WeakReference 的子类)、ConcurrentLinkedQueue 和一个基于 K 的值对象反序列化定义接口组合在一起,以正确运行 LRU 缓存。队列保存强引用,GC 会在适当时候从内存中清除值。跟踪队列大小涉及 AtomicInteger,因为您无法真正检查队列以确定何时进行清除。缓存将处理从/添加到队列的驱逐,以及映射管理。如果 GC 从内存中清除了值,则反序列化接口的实现将处理检索值的操作。我还有另一种实现方法,涉及将数据写入磁盘并重新读取,但与我在此发布的解决方案相比,它要慢得多,因为我必须同步写入/读取。
您提到希望使用“线程安全”的替代方案来解决可扩展性问题。这里的“线程安全”意味着该结构对并发访问的尝试是容忍的,它不会在没有外部同步的情况下因并发使用而遭受破坏。然而,这种容忍并不一定有助于改善“可扩展性”。在最简单的(虽然通常是误导的)方法中,您将尝试在内部同步您的结构,并仍然保留非原子的“检查-然后-操作”操作不安全。
LRU缓存至少需要对总体结构有一定的了解。它们需要类似成员计数或成员大小的东西来决定何时驱逐元素,然后需要能够协调驱逐和并发尝试读取、添加或删除元素。试图减少对“主”结构的并发访问所需的同步会影响您的驱逐机制,并迫使您的驱逐策略在其保证方面变得不那么精确。
当前接受的答案提到“当您想要驱逐一个条目时”。这就是难点所在。您如何知道何时要驱逐一个条目?您需要暂停哪些其他操作才能做出这个决定?
将地图包装在Collections.synchronizedMap()
中。如果您需要调用其他方法,则在从此调用返回的地图上进行同步
,并在原始地图上调用原始方法(请参见Javadocs示例)。当您遍历键等时,也是如此。