如何使用LinkedHashMap中的类似功能实现ConcurrentHashMap?

时间:2009-11-29 14:17:53

标签: java performance multithreading concurrenthashmap linkedhashmap

我已将LinkedHashMapaccessOrder一起使用,并且随时允许最多500个条目作为LRU数据缓存。但由于可扩展性问题,我想转向一些线程安全的替代方案。 ConcurrentHashMap在这方面似乎很好,但缺少accessOrderremoveEldestEntry(Map.Entry e)LinkedHashMap的功能。任何人都可以指向某些链接或帮助我简化实施。

6 个答案:

答案 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)。迭代键等时也是如此。

相关问题