【问题标题】:How to implement ConcurrentHashMap with features similar in LinkedHashMap?如何实现具有类似于 LinkedHashMap 的功能的 ConcurrentHashMap?
【发布时间】:2010-12-21 09:36:42
【问题描述】:

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

【问题讨论】:

标签: java performance multithreading concurrenthashmap linkedhashmap


【解决方案1】:

将地图包装在Collections.synchronizedMap() 中。如果您需要调用其他方法,则在您从此调用返回的地图上synchronize,并在原始地图上调用原始方法(see the javadocs for an example)。当您遍历键等时也是如此。

【讨论】:

  • Aaron 这会恶化我的可扩展性问题。由于地图的更新本质上将成为连续的。 ConcurrentHashMap 具有多个功能可以同时更新地图。
  • ConcHM 有一些特定的属性,这些属性不容易映射到 LinkedHM(即,只要哈希不映射到相同的段键,它就允许同时插入)。当您需要保持插入顺序时,无法进行此优化。您必须决定哪一项对您更重要。
【解决方案2】:

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

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

更新:该领域的其他可能性包括 Java Collections synchronization wrapper 和 Java 1.6 ConcurrentSkipListMap

【讨论】:

  • 您的建议似乎很符合我的需要。这意味着我有一些开销来保留其他元素,这些元素将消耗与地图集相同的大小。无论如何,这对我来说很好。
  • 我知道已经有一段时间了,但我找到了你的答案,你能告诉我你什么时候得到了 ArrayList 被驱逐的条目,那么你如何从 ConcurrentHashMap 中删除它们?您是否遍历 Map.Entry 检查值是否相同,然后使用其键将其从 Map 中删除?还是我错过了什么而你用另一种方式做?
【解决方案3】:

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

【讨论】:

  • 还没有。但 EHcache 将是我的另一个选择。
  • 我们已经尝试过使用 LinkedHashMap(maxSize, true) 的 ReentrantReadWrite Locks,但是你会得到 ConcurrentModificationExceptions(get 会修改地图结构),并且性能远不如 ConcurrentHashMap。
【解决方案4】:

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

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

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

    • 谢谢,很高兴将您的解决方案添加到帖子中,即使是旧的
    【解决方案6】:

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-10-05
      • 1970-01-01
      • 1970-01-01
      • 2021-07-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多