【问题标题】:Implementing a concurrent LinkedHashMap实现并发的 LinkedHashMap
【发布时间】:2011-02-11 11:55:15
【问题描述】:

我正在尝试为多线程架构创建并发 LinkedHashMap。

如果我使用Collections#synchronizedMap(),我将不得不使用同步块进行迭代。这种实现会导致元素的顺序添加。

如果我使用ConcurrentSkipListMap,有没有办法实现Comparator 以顺序存储,如存储在链接列表或队列中。

我想使用 java 的内置而不是第三方包。

编辑:

在这个并发的LinkedHashMap 中,如果键是名称,我希望按它们到达的顺序放置键。即新值将被附加到开始或结束,但顺序。

在迭代时,LinkedHashMap 可以添加新条目,也可以删除。但迭代应该是添加条目的顺序。

我了解,通过使用Collections#synchronizedMap(),必须实现用于迭代的同步块,但映射在迭代时是否可以修改(可以添加/删除条目)。

【问题讨论】:

  • 你在说什么getter/setter?你想“按顺序存储”什么?请提供更多详细信息 - 目前很难理解您真正想要做什么。
  • 你想按顺序存储什么?如果您的密钥具有可定义的顺序,那么您应该能够编写一个比较器。也许您应该提供有关地图中的键以及您希望它们如何排序的更多信息。

标签: java collections concurrency


【解决方案1】:

如果使用 synchronizedMap,则不必在外部进行同步,迭代除外。如果您需要保留地图的顺序,则应使用 SortedMap。您可以使用 ConcurrentSkipListMap,它是线程安全的,或者另一个 SortedMap 与 synchronizedSortedMap 结合使用。

【讨论】:

    【解决方案2】:

    LinkedHashMap 有一个通过哈希表运行的双向链表。 FIFO 仅在写入(插入或删除)时改变链接。这使得实现一个版本相当简单。

    1. 编写一个只允许插入顺序的 LHM。
    2. 切换到 ConcurrentHashMap 作为哈希表。
    3. 用锁保护#put() / #putIfAbsent() / #remove()
    4. 使“下一个”字段可变。

    在迭代时,不需要锁定,因为您可以安全地跟随“下一个”字段。只需在#get() 上委派给 CHM,就可以无锁读取。

    【讨论】:

    • 无意冒犯,但我讨厌人们这样说话。我知道什么是双向链表。我知道什么是哈希表。但是通过哈希表运行的双链表应该意味着什么?
    • 是的,它很难解释和想象。看到这个presentation 我试了一下。这个想法是链表保持顺序并且需要 O(n) 查找。哈希表是无序的,并在 O(1) 时间内找到一个条目。组合是哈希表条目包含列表指针,因此条目在列表中排序。列表中的条目可以附加到(FIFO 顺序),移动到头部/尾部(LRU 顺序),并在 O(1) 时间内删除(驱逐)。这可以廉价地提供插入或访问有序映射。
    • 如果我对我的HashMap 进行排序,它是否会有效地变成一个“有序”的哈希图,这基本上违背了LinkedHashMap 的目的?
    • 任何基于比较的排序都是 O(lg n) 并且需要比较的东西(例如时间戳)。这不是基于比较的,并且可以更快地维护插入或访问顺序。
    【解决方案3】:

    使用Collections#synchronizedMap()

    根据我的看法,如果我使用 Collections.synchronizedMap(),我将不得不为 getter/setter 使用同步块。

    这不是真的。您只需要在任何视图(键集、值、条目集)上同步迭代。另请参阅上面链接的 API 文档。

    【讨论】:

    • 如果地图正在迭代,那么其他线程是否可以从地图中添加或删除元素?
    • 这种实现会导致元素的顺序添加
    • 1) 如果您按照文档同步迭代,则不会(您是否点击了链接?)。这就是同步的全部意义所在。 2) 是的,添加已经在内部同步,这也是你的全部意图,是吗?
    • 1) 否 - 阅读 javadoc。 2) 是的 - 这由 javadoc 暗示,并由当前实现保证。
    • 可以在不序列化所有 get 和 put 操作的情况下进行同步。例如,ConcurrentHashMap 就是这样工作的。但是,javadoc 暗示此方法返回的 Map 上的操作被序列化。
    【解决方案4】:

    到目前为止,我的项目使用来自 Apache Collections 的 LRUMap,但它基于 SequencedHashMap。 Collections 建议 ListOrderedMap 但没有一个是线程安全的。

    我已从Google Guava 切换到MapMaker。你也可以看看CacheBuilder

    【讨论】:

    【解决方案5】:

    嗯,简单的答案是使用您的Comparator 操作的单调递增的密钥提供程序。想想AtomicInteger,每次插入时,都会创建一个用于比较的新键。如果你汇集你的真实密钥,你可以制作一个OrderedKey<MyRealKeyType>的内部映射。

    class OrderedKey<T> implements Comparable<OrderedKey<T>> {
      T realKey;
      int index;
      OrderedKey(AtomicInteger source, T key) {
        index = source.getAndIncrement();
        realKey = key;
      }
      public int compareTo(OrderedKey<T> other) {
        if (Objects.equals(realKey, other.realKey)) {
          return 0;
        }
        return index - other.index;
      }
    
    
    }
    

    这将消除对自定义比较器的需求,并为您提供一个很好的 O(1) 方法来计算大小(除非您允许删除,在这种情况下,也要计算这些,所以您可以减去“所有成功的删除" 来自“所有成功的添加”,其中成功意味着实际创建或删除了一个条目)。

    【讨论】:

    • 小心内存泄漏;您可能想将 WeakReference 添加到昂贵的东西......虽然昂贵的东西往往作为地图中的键非常糟糕,但有时您只需要一个动态的元组列表(并且懒得为其创建一个真正的对象),并且java缺乏优雅地表达这一点的语法。当然,使用实际的映射语义,您需要一个线程安全的插入有序映射,OrderedKey 为您启用。
    猜你喜欢
    • 2015-06-20
    • 1970-01-01
    • 1970-01-01
    • 2015-02-13
    • 2013-11-13
    • 2011-03-02
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多