【问题标题】:Why should we use HashMap in multi-threaded environments?为什么要在多线程环境中使用 HashMap?
【发布时间】:2013-07-27 16:08:57
【问题描述】:

今天我正在阅读有关 HashMap 在 Java 中的工作原理。我遇到了一个博客,我直接引用了博客的文章。我已经阅读了关于 Stackoverflow 的 this 文章。仍然 我想知道细节。

所以答案是肯定的,存在潜在的竞争条件,而 在Java中调整HashMap的大小,如果两个线程同时发现 现在 HashMap 需要调整大小,他们都尝试调整大小。在 Java中HashMap的大小调整过程,bucket中的元素 存储在链表中,在迁移期间按顺序颠倒 到新存储桶,因为 java HashMap 不会将新元素附加到 tail 而是在头部附加新元素以避免尾部遍历。 如果发生竞态条件,那么您将最终陷入无限循环。

它指出,由于 HashMap 在调整 HashMap 大小期间不是线程安全的,因此存在潜在的竞争 情况可能发生。我什至在我们的办公项目中看到,人们广泛使用 HashMaps 知道它们不是线程安全的。如果不是线程安全的,为什么要使用HashMap 然后?是否只是开发人员缺乏知识,因为他们可能不了解 ConcurrentHashMap 之类的结构或其他原因。谁能解释一下这个谜题。

【问题讨论】:

  • 使结构线程安全需要额外的运行时工作(CPU 周期)。因此,如果线程安全不是问题,HashMap 效率更高
  • 如果是共享的话不是一个好选择,但是如果限制在单线程上也没问题...两种情况都有用例...
  • 如果你能保证对那个HashMap的单线程访问,你就可以安全地使用它。否则,开始问自己问题...

标签: java hashmap synchronized


【解决方案1】:

在多线程环境中使用 HashMap 的一种解决方法是使用预期的对象计数对其进行初始化,从而避免重新调整大小。

【讨论】:

    【解决方案2】:

    当单个线程可以访问它时,可以使用 Hashmap。然而,当多个线程开始访问 Hashmap 时,将出现 2 个主要问题: 1. 调整 hashmap 的大小不能保证按预期工作。 2. 会抛出并发修改异常。当单线程访问它同时读取和写入hashmap时,也会抛出这个问题。

    【讨论】:

      【解决方案3】:

      我做了更多的研究,我想说所有的答案都很好。但我们显然可以, 看看为什么在 HashMap 中会出现竞争条件。在对 stackoverflow 进行了一些研究之后,我找到了这些参考资料,它们非常值得进一步研究这个概念。

      1. Why a race condition occurs in HashMap
      2. Is Concurrent Hashmap better then Hashmap
      3. Multi-threaded environment while doing resizing

      我想他们已经阐明了我的概念。

      【讨论】:

        【解决方案4】:

        这有几个方面:首先,大多数集合都不是线程安全的。如果你想要一个线程安全的集合,你可以调用synchronizedCollectionsynchronizedMap

        但要点是:您希望您的线程并行运行,根本不同步——当然如果可能的话。这是您应该努力的目标,但当然不可能在每次处理多线程时都实现。 但是使默认的集合/映射线程安全没有意义,因为它应该是共享映射的边缘情况。同步意味着 jvm 需要做更多的工作。

        【讨论】:

        • 你不能总是让你的线程彼此独立运行,资源共享不是一种极端情况。如果线程不共享任何内容,它会有什么好处?那是边缘情况。考虑将问题分解为子问题的大多数科学问题,或等待输入(然后将其卸载到处理器线程)的侦听器线程。
        • 当然,您必须不时共享数据,但您应该尽量不要这样做,因为每次同步您都会失去拥有多个线程的好处。这就是我的意思。
        【解决方案5】:

        在多线程环境中,您必须确保它不会被同时修改,否则您可能会遇到严重的内存问题,因为它没有以任何方式同步。

        亲爱的,之前检查一下 Api,我也有同样的想法。

        我认为解决方案是使用静态 Collections.synchronizedMap 方法。我期待它能够返回更好的实现。但是,如果您查看源代码,您会发现它们在其中所做的只是一个包装器,其中包含对互斥锁的同步调用,这恰好是同一个映射,不允许同时发生读取。

        在 Jakarta commons 项目中,有一个称为 FastHashMap 的实现。这个实现有一个叫做快速的属性。如果 fast 为 true,则读取是非同步的,写入将执行以下步骤:

        Clone the current structure
        Perform the modification on the clone
        Replace the existing structure with the modified clone 
        public class FastSynchronizedMap implements Map,   
        Serializable {
        
        private final Map m;
        private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
        
        .
        .
        .
        
        public V get(Object key) {
        lock.readLock().lock();
        V value = null;
        try {
            value = m.get(key);
        } finally {
            lock.readLock().unlock();
        }
        return value;
        }
        
        public V put(K key, V value) {
        lock.writeLock().lock();
        V v = null;
        try {
            v = m.put(key, value);
        } finally {
            lock.writeLock().lock();
        }
        return v;
        }
        
        .
        .
        .
        }
        

        注意我们做了一个try finally块,我们要保证无论块中遇到什么问题都会释放锁。

        当您几乎没有写操作而主要是读操作时,此实现效果很好。

        【讨论】:

          【解决方案6】:

          我可以自信地说 ConcurrentHashMap 是一个相当被忽视的类。没有多少人知道它,也没有多少人愿意使用它。该类提供了一种非常健壮且快速的同步 Map 集合的方法。我在网上阅读了一些 HashMap 和 ConcurrentHashMap 的比较。让我说他们完全错了。您无法将两者进行比较,一种提供同步方法来访问地图,而另一种则不提供任何同步。

          我们大多数人没有注意到的是,虽然我们的应用程序,尤其是 Web 应用程序,在开发和测试阶段运行良好,但它们通常会在重载(甚至中等重载)下向上倾斜。这是因为我们希望我们的 HashMap 以某种方式表现,但在负载下它们通常表现不佳。 Hashtable 提供对其条目的并发访问,但需要注意的是,整个地图都被锁定以执行任何类型的操作。

          虽然这种开销在正常负载下的 Web 应用程序中是可以忽略的,但在负载过重的情况下,它可能会导致响应时间延迟和服务器负担过重。这就是 ConcurrentHashMap 介入的地方。它们提供了 Hashtable 的所有功能,性能几乎与 HashMap 一样好。 ConcurrentHashMap 通过一种非常简单的机制来实现这一点。

          默认情况下,集合维护一个包含 16 个锁的列表,而不是地图范围的锁,每个锁用于保护(或锁定)地图的单个存储桶。这实际上意味着 16 个线程可以一次修改集合(只要它们都在不同的存储桶上工作)。事实上,这个集合并没有执行任何锁定整个地图的操作。

          【讨论】:

          • ConcurrentHashMap 中的锁不在桶级别。它们处于分区级别。每个分区包含几个桶。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2021-06-01
          • 2010-11-02
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多