ConcurrentHashMap和Collections.synchronizedMap(Map)有什么区别?

时间:2009-02-04 09:22:47

标签: java dictionary concurrency

我有一个Map,它将被多个线程同时修改。

Java API中似乎有三种不同的同步Map实现:

  • Hashtable
  • Collections.synchronizedMap(Map)
  • ConcurrentHashMap

据我所知,Hashtable是一个旧的实现(扩展过时的Dictionary类),后来经过调整以适应Map接口。当 同步时,它似乎有严重的scalability issues,并且不鼓励新项目。

但是另外两个呢? Collections.synchronizedMap(Map)ConcurrentHashMap返回的地图之间有何区别?哪一种适合哪种情况?

19 个答案:

答案 0 :(得分:403)

根据您的需要,使用ConcurrentHashMap。它允许从多个线程并发地修改Map,而无需阻止它们。 Collections.synchronizedMap(map)会创建一个阻止性能的地图,虽然可以确保一致性(如果使用得当)。

如果需要确保数据一致性,请使用第二个选项,并且每个线程都需要具有最新的地图视图。如果性能至关重要,请使用第一个,并且每个线程仅将数据插入到地图中,读取的频率会降低。

答案 1 :(得分:224)

╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   Property    ║     HashMap       ║    Hashtable      ║  ConcurrentHashMap  ║
╠═══════════════╬═══════════════════╬═══════════════════╩═════════════════════╣ 
║      Null     ║     allowed       ║              not allowed                ║
║  values/keys  ║                   ║                                         ║
╠═══════════════╬═══════════════════╬═════════════════════════════════════════╣
║Is thread-safe ║       no          ║                  yes                    ║
╠═══════════════╬═══════════════════╬═══════════════════╦═════════════════════╣
║     Lock      ║       not         ║ locks the whole   ║ locks the portion   ║        
║  mechanism    ║    applicable     ║       map         ║                     ║ 
╠═══════════════╬═══════════════════╩═══════════════════╬═════════════════════╣
║   Iterator    ║               fail-fast               ║ weakly consistent   ║ 
╚═══════════════╩═══════════════════════════════════════╩═════════════════════╝

关于锁定机制: Hashtable locks the object,而ConcurrentHashMap锁定only the bucket

答案 2 :(得分:140)

Hashtable中的“可伸缩性问题”在Collections.synchronizedMap(Map)中以完全相同的方式出现 - 它们使用非常简单的同步,这意味着只有一个线程可以同时访问地图。

当您进行简单的插入和查找时(除非您非常密集地执行此操作),这不是什么大问题,但是当您需要遍历整个Map时会成为一个大问题,这可能需要很长时间才能完成映射 - 当一个线程执行此操作时,所有其他线程必须等待,如果他们想要插入或查找任何内容。

ConcurrentHashMap使用非常复杂的技术来减少同步的需要,并允许多个线程进行并行读取访问而不进行同步,更重要的是,提供了不需要同步的Iterator甚至允许Map在交互期间进行修改(虽然它不能保证是否会返回在迭代期间插入的元素)。

答案 3 :(得分:33)

当您可以使用它时,首选ConcurrentHashMap - 尽管它至少需要Java 5。

它被设计为在被多个线程使用时可以很好地扩展。当只有一个线程一次访问Map时性能可能稍微差一些,但是当多个线程同时访问地图时,性能会明显提高。

我找到了blog entry,可以从优秀的书Java Concurrency In Practice中重现一张表,我完全推荐这本书。

Collections.synchronizedMap真的很有意义,只有当你需要用一些其他特征包装一个地图时,可能是某种有序的地图,比如TreeMap。

答案 4 :(得分:32)

这两者之间的主要区别在于ConcurrentHashMap将仅锁定正在更新的部分数据,而其他部分数据可以被其他线程访问。但是,Collections.synchronizedMap()将在更新时锁定所有数据,其他线程只能在释放锁时访问数据。如果有许多更新操作和相对少量的读取操作,则应选择ConcurrentHashMap

另外一个区别是ConcurrentHashMap不会保留传入的Map中元素的顺序。它在存储数据时类似于HashMap。无法保证元素顺序得以保留。虽然Collections.synchronizedMap()会保留传入的地图的元素顺序。例如,如果您将TreeMap传递给ConcurrentHashMap,则ConcurrentHashMap中的元素顺序可能不是与TreeMap中的订单相同,但Collections.synchronizedMap()将保留订单。

此外,ConcurrentHashMap可以保证在一个线程更新映射而另一个线程遍历从映射获得的迭代器时没有抛出ConcurrentModificationException。但是,我们无法保证Collections.synchronizedMap()

one post证明了这两者以及ConcurrentSkipListMap之间的差异。

答案 5 :(得分:12)

ConcurrentHashMap中,锁定应用于细分而非整个地图。 每个段管理自己的内部哈希表。锁仅适用于更新操作。 Collections.synchronizedMap(Map)同步整个地图。

答案 6 :(得分:11)

像往常一样,涉及并发 - 开销 - 速度权衡。您确实需要考虑应用程序的详细并发要求来做出决定,然后测试代码以确定它是否足够好。

答案 7 :(得分:9)

你对HashTable是正确的,你可以忘掉它。

Your article提到了这样一个事实:虽然HashTable和同步包装类通过一次允许一个线程访问映射来提供基本的线程安全性,但这不是“真正的”线程安全性,因为许多复合操作仍然需要额外的同步,例如:

synchronized (records) {
  Record rec = records.get(id);
  if (rec == null) {
      rec = new Record(id);
      records.put(id, rec);
  }
  return rec;
}

但是,不要认为ConcurrentHashMap是具有典型HashMap块的synchronized的简单替代方法,如上所示。阅读this文章,以更好地了解其错综复杂的内容。

答案 8 :(得分:9)

同步地图:

同步映射与Hashtable也没有太大差别,并且在并发Java程序中提供类似的性能。 Hashtable和SynchronizedMap之间的唯一区别是SynchronizedMap不是遗留的,您可以使用Collections.synchronizedMap()方法包装任何Map以创建它的同步版本。

<强>的ConcurrentHashMap:

ConcurrentHashMap类提供标准HashMap的并发版本。这是对Collections类中提供的synchronizedMap功能的改进。

与Hashtable和Synchronized Map不同,它永远不会锁定整个Map,而是将段分割成段,并对其进行锁定。如果读取器线程数大于写入程序线程数,则性能会更好。

默认情况下,ConcurrentHashMap被分为16个区域并应用锁定。初始化ConcurrentHashMap实例时,可以设置此默认编号。在特定段中设置数据时,将获取该段的锁定。这意味着如果两个更新各自影响单独的存储桶,则它们仍可以同时安全地执行,从而最大限度地减少锁争用,从而最大限度地提高性能。

ConcurrentHashMap不会抛出ConcurrentModificationException

如果一个线程试图修改它而另一个线程迭代它,则ConcurrentHashMap不会抛出ConcurrentModificationException

synchornizedMap和ConcurrentHashMap之间的区别

Collections.synchornizedMap(HashMap)将返回一个几乎等同于Hashtable的集合,其中Map上的每个修改操作都锁定在Map对象上,而在ConcurrentHashMap的情况下,通过将整个Map划分为不同的分区来实现线程安全性在并发级别上,只锁定特定部分而不是锁定整个Map。

当同步HashMap允许一个空键时,ConcurrentHashMap不允许使用null键或空值。

类似链接

Link1

Link2

Performance Comparison

答案 9 :(得分:7)

以下几点:

1)ConcurrentHashMap仅锁定Map的一部分,但SynchronizedMap锁定整个MAp 2)ConcurrentHashMap比SynchronizedMap具有更好的性能,并且具有更高的可扩展性 3)如果是多个读者和单个编写者,ConcurrentHashMap是最佳选择。

此文字来自Difference between ConcurrentHashMap and hashtable in Java

答案 10 :(得分:7)

我们可以使用ConcurrentHashMap和synchronisedHashmap以及Hashtable来实现线程安全。但如果你看一下他们的架构,会有很多不同。

  1. synchronisedHashmap和Hashtable
  2.   

    两者都将保持对象级别的锁定。所以如果你想执行像put / get这样的任何操作,那么你必须首先获得锁。同时,不允许其他线程执行任何操作。所以一次只有一个线程可以对此进行操作。所以等待时间会增加。我们可以说在与ConcurrentHashMap进行比较时性能相对较低。

    1. 的ConcurrentHashMap
    2.   

      它将保持段级别的锁定。它有16个段,默认情况下将并发级别维持在16。所以一次,16个线程可以在ConcurrentHashMap上运行。而且,读操作不需要锁定。因此任意数量的线程都可以对其执行get操作。

           

      如果thread1想要在段2中执行put操作并且thread2想要在段4上执行put操作,那么这里允许它。意味着,16个线程可以一次对ConcurrentHashMap执行更新(put / delete)操作。

           

      这样等待时间会减少。因此,性能相对于synchronisedHashmap和Hashtable相对更好。

答案 11 :(得分:6)

<强>的ConcurrentHashMap

  • 如果项目中需要非常高的并发性,则应使用ConcurrentHashMap。
  • 不同步整个地图是线程安全的。
  • 使用锁定进行写入时,读取速度可能非常快。
  • 对象级别没有锁定。
  • 在hashmap桶级别,锁定的粒度更精细。
  • 如果一个线程试图修改它而另一个线程迭代它,则ConcurrentHashMap不会抛出ConcurrentModificationException。
  • ConcurrentHashMap使用多种锁。

<强> SynchronizedHashMap

  • 对象级别的同步。
  • 每次读/写操作都需要获取锁定。
  • 锁定整个集合是一种性能开销。
  • 这实际上只允许访问整个地图的一个线程。阻止所有其他线程。
  • 可能会导致争用。
  • SynchronizedHashMap返回Iterator,它在并发修改时失败。

source

答案 12 :(得分:4)

ConcurrentHashMap针对并发访问进行了优化。

访问不会锁定整个地图,而是使用更精细的策略,从而提高可扩展性。还存在专门用于并发访问的功能性增强,例如,并发迭代器。

答案 13 :(得分:3)

  1. 如果数据一致性非常重要 - 请使用Hashtable或Collections.synchronizedMap(Map)。
  2. 如果速度/性能非常重要且数据更新可能会受到影响 - 请使用ConcurrentHashMap。

答案 14 :(得分:3)

一个关键功能需要注意ConcurrentHashMap除了它提供的并发功能,它是故障安全迭代器。我见过开发人员使用ConcurrentHashMap只是因为他们想要编辑入口集 - 在迭代时放置/删除。 Collections.synchronizedMap(Map)不提供故障安全迭代器,但它提供了 fail-fast 迭代器。失败快速迭代器使用迭代过程中无法编辑的映射大小的快照。

答案 15 :(得分:1)

Collections.synchronizedMap()方法同步HashMap的所有方法,并有效地将其简化为一个线程一次可以输入的数据结构,因为它将每个方法锁定在公共锁上。

在ConcurrentHashMap中,同步完成的方式略有不同。 ConcurrentHashMap不是将每个方法锁定在公共锁上,而是对单独的桶使用单独的锁,因此只锁定Map的一部分。 默认情况下,有16个存储桶以及用于单独存储桶的单独锁。所以默认的并发级别是16.这意味着理论上任何给定的时间16个线程都可以访问ConcurrentHashMap,如果它们都要分离存储桶的话。

答案 16 :(得分:1)

一般情况下,如果您想使用ConcurrentHashMap,请确保您已准备好错过“更新”(即打印HashMap的内容并不能确保它会打印出最新版本)映射)并使用CyclicBarrier之类的API来确保整个程序生命周期的一致性。

答案 17 :(得分:1)

ConcurrentHashMap是Java 1.5中Hashtable的替代品,是并发包的一部分。使用ConcurrentHashMap,不仅可以在并发多线程环境中安全地使用它,而且比Hashtable和syncedMap具有更好的性能,您还有更好的选择。 ConcurrentHashMap性能更好,因为它锁定了Map的一部分。它允许并发的读取操作,同时通过同步写入操作保持完整性。

如何实现ConcurrentHashMap

ConcurrentHashMap是作为Hashtable的替代产品开发的,并具有附加功能,即所谓的并发级别,支持Hashtable的所有功能。 ConcurrentHashMap允许多个读取器同时读取而不使用块。通过将Map划分为不同的部分并在更新中仅阻止Map的一部分,就可以实现。默认情况下,并发级别为16,因此Map被拆分为16个部分,每个部分由单独的块管理。这意味着,如果16个线程与Map的不同部分一起工作,则它们可以同时与Map一起工作。它使ConcurrentHashMap的生产率更高,并且不会降低线程安全性。

如果您对ConcurrentHashMap的一些重要功能感兴趣,以及何时应使用Map的这种实现方式-我只链接了一篇不错的文章-How to use ConcurrentHashMap in Java

答案 18 :(得分:0)

除了建议之外,我还要发布与SynchronizedMap相关的源代码。

为了使Map线程安全,我们可以使用Collections.synchronizedMap语句并输入map实例作为参数。

synchronizedMapCollections的实施情况如下所示

   public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
        return new SynchronizedMap<>(m);
    }

如您所见,输入Map对象由SynchronizedMap对象包装 让我们深入研究SynchronizedMap

的实现
 private static class SynchronizedMap<K,V>
        implements Map<K,V>, Serializable {
        private static final long serialVersionUID = 1978198479659022715L;

        private final Map<K,V> m;     // Backing Map
        final Object      mutex;        // Object on which to synchronize

        SynchronizedMap(Map<K,V> m) {
            this.m = Objects.requireNonNull(m);
            mutex = this;
        }

        SynchronizedMap(Map<K,V> m, Object mutex) {
            this.m = m;
            this.mutex = mutex;
        }

        public int size() {
            synchronized (mutex) {return m.size();}
        }
        public boolean isEmpty() {
            synchronized (mutex) {return m.isEmpty();}
        }
        public boolean containsKey(Object key) {
            synchronized (mutex) {return m.containsKey(key);}
        }
        public boolean containsValue(Object value) {
            synchronized (mutex) {return m.containsValue(value);}
        }
        public V get(Object key) {
            synchronized (mutex) {return m.get(key);}
        }

        public V put(K key, V value) {
            synchronized (mutex) {return m.put(key, value);}
        }
        public V remove(Object key) {
            synchronized (mutex) {return m.remove(key);}
        }
        public void putAll(Map<? extends K, ? extends V> map) {
            synchronized (mutex) {m.putAll(map);}
        }
        public void clear() {
            synchronized (mutex) {m.clear();}
        }

        private transient Set<K> keySet;
        private transient Set<Map.Entry<K,V>> entrySet;
        private transient Collection<V> values;

        public Set<K> keySet() {
            synchronized (mutex) {
                if (keySet==null)
                    keySet = new SynchronizedSet<>(m.keySet(), mutex);
                return keySet;
            }
        }

        public Set<Map.Entry<K,V>> entrySet() {
            synchronized (mutex) {
                if (entrySet==null)
                    entrySet = new SynchronizedSet<>(m.entrySet(), mutex);
                return entrySet;
            }
        }

        public Collection<V> values() {
            synchronized (mutex) {
                if (values==null)
                    values = new SynchronizedCollection<>(m.values(), mutex);
                return values;
            }
        }

        public boolean equals(Object o) {
            if (this == o)
                return true;
            synchronized (mutex) {return m.equals(o);}
        }
        public int hashCode() {
            synchronized (mutex) {return m.hashCode();}
        }
        public String toString() {
            synchronized (mutex) {return m.toString();}
        }

        // Override default methods in Map
        @Override
        public V getOrDefault(Object k, V defaultValue) {
            synchronized (mutex) {return m.getOrDefault(k, defaultValue);}
        }
        @Override
        public void forEach(BiConsumer<? super K, ? super V> action) {
            synchronized (mutex) {m.forEach(action);}
        }
        @Override
        public void replaceAll(BiFunction<? super K, ? super V, ? extends V> function) {
            synchronized (mutex) {m.replaceAll(function);}
        }
        @Override
        public V putIfAbsent(K key, V value) {
            synchronized (mutex) {return m.putIfAbsent(key, value);}
        }
        @Override
        public boolean remove(Object key, Object value) {
            synchronized (mutex) {return m.remove(key, value);}
        }
        @Override
        public boolean replace(K key, V oldValue, V newValue) {
            synchronized (mutex) {return m.replace(key, oldValue, newValue);}
        }
        @Override
        public V replace(K key, V value) {
            synchronized (mutex) {return m.replace(key, value);}
        }
        @Override
        public V computeIfAbsent(K key,
                Function<? super K, ? extends V> mappingFunction) {
            synchronized (mutex) {return m.computeIfAbsent(key, mappingFunction);}
        }
        @Override
        public V computeIfPresent(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.computeIfPresent(key, remappingFunction);}
        }
        @Override
        public V compute(K key,
                BiFunction<? super K, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.compute(key, remappingFunction);}
        }
        @Override
        public V merge(K key, V value,
                BiFunction<? super V, ? super V, ? extends V> remappingFunction) {
            synchronized (mutex) {return m.merge(key, value, remappingFunction);}
        }

        private void writeObject(ObjectOutputStream s) throws IOException {
            synchronized (mutex) {s.defaultWriteObject();}
        }
    }

SynchronizedMap的作用可归纳为向输入Map对象的主方法添加单个锁。由锁保护的所有方法不能同时被多个线程访问。这意味着putget等正常操作可以由Map对象中的所有数据同时由单个线程执行。

它使Map对象线程现在安全,但在某些情况下性能可能会成为一个问题。

ConcurrentMap在实施过程中要复杂得多,我们可以参考Building a better HashMap了解详情。简而言之,它的实现考虑了线程安全和性能。