我已将LinkedHashMap
与accessOrder
一起使用,并且随时允许最多500个条目作为LRU数据缓存。但由于可扩展性问题,我想转向一些线程安全的替代方案。 ConcurrentHashMap
在这方面似乎很好,但缺少accessOrder
中removeEldestEntry(Map.Entry e)
和LinkedHashMap
的功能。任何人都可以指向某些链接或帮助我简化实施。
答案 0 :(得分:10)
我最近使用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集合synchronization wrapper和Java 1.6 ConcurrentSkipListMap。
答案 1 :(得分:2)
您是否尝试过使用ehcache等众多缓存解决方案之一? 您可以尝试将LinkedHashMap与ReadWriteLock一起使用。这将为您提供并发读访问权。
答案 2 :(得分:2)
现在看起来似乎已经老了,但至少只是为了我自己的历史跟踪,我将在这里添加我的解决方案:我结合了映射K-&gt; WeakReference的子类的ConcurrentHashMap,ConcurrentLinkedQueue和定义反序列化的接口基于K的值对象正确运行LRU缓存。队列中包含强引用,GC将在适当时从内存中逐出值。跟踪AtomicInteger所涉及的队列大小,因为您无法真正检查队列以确定何时驱逐。缓存将处理驱逐/添加到队列以及地图管理。如果GC从内存中清除了值,则反序列化接口的实现将处理检索值。我还有另一个实现,涉及假脱机到磁盘/重新读取被假脱机的内容,但这比我在这里发布的解决方案要慢得多,因为我要同步假脱机/读取。
答案 3 :(得分:0)
您提到希望使用“线程安全”替代方案来解决可伸缩性问题。这里的“线程安全”意味着该结构对并发访问的尝试容忍,因为它不会因没有外部同步的并发使用而受到损害。然而,这种容忍度不一定有助于改善“可扩展性”。在最简单 - 尽管通常是误导的 - 方法中,您将尝试在内部同步您的结构,并仍然使非原子 check-then-act 操作不安全。
LRU缓存至少需要了解总体结构。他们需要像成员的数量或成员的大小来决定何时驱逐,然后他们需要能够协调驱逐与并发尝试读取,添加或删除元素。试图减少并发访问“主要”结构所需的同步与你的驱逐机制相抗制,并迫使你的驱逐政策在其保证中不那么精确。
目前接受的答案提到“当你想要驱逐一个条目”。这就是摩擦。你怎么知道什么时候想要逐出一个条目?您需要暂停哪些其他操作才能做出此决定?
答案 4 :(得分:0)
当您将其他数据结构与并发哈希图一起使用时,操作的原子性(例如在并发哈希图中添加新项并添加其他数据结构)将无法得到保证,而没有诸如ReadWriteLock之类的额外同步会降低性能
答案 5 :(得分:-2)
将地图包裹在Collections.synchronizedMap()
中。如果您需要调用其他方法,然后在地图上调用synchronize
,并在原始地图上调用原始方法(see the javadocs for an example)。迭代键等时也是如此。