我有一个映射,这是由几个线程并发修改。

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

哈希表 collections . synchronizedmap(地图) ConcurrentHashMap

根据我的理解,Hashtable是一个旧的实现(扩展了过时的Dictionary类),后来为了适应Map接口而进行了调整。虽然它是同步的,但它似乎有严重的可伸缩性问题,不推荐用于新项目。

那另外两个呢?Collections.synchronizedMap(Map)和ConcurrentHashMaps返回的Map之间有什么区别?哪一种适合哪种情况?


当前回答

除了ConcurrentHashMap提供的并发特性之外,还有一个重要的特性需要注意,那就是故障安全迭代器。我见过开发人员使用ConcurrentHashMap只是因为他们想编辑条目集——在迭代时放置/删除它。 synchronizedmap (Map)不提供故障安全迭代器,但它提供了快速故障迭代器。快速失败迭代器使用映射大小的快照,在迭代过程中不能编辑。

其他回答

一般来说,如果你想使用ConcurrentHashMap,确保你已经准备好错过“更新”(即打印HashMap的内容并不能确保它会打印最新的Map),并使用CyclicBarrier等api来确保程序生命周期的一致性。

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

Also one other difference is that ConcurrentHashMap will not preserve the order of elements in the Map passed in. It is similar to HashMap when storing data. There is no guarantee that the element order is preserved. While Collections.synchronizedMap() will preserve the elements order of the Map passed in. For example, if you pass a TreeMap to ConcurrentHashMap, the elements order in the ConcurrentHashMap may not be the same as the order in the TreeMap, but Collections.synchronizedMap() will preserve the order.

此外,ConcurrentHashMap可以保证当一个线程更新映射而另一个线程遍历从映射中获得的迭代器时,不会抛出ConcurrentModificationException。但是,Collections.synchronizedMap()在此上不被保证。

有一篇文章展示了这两者的区别,还有ConcurrentSkipListMap。

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

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

为了使Map线程安全,我们可以使用集合。synchronizedMap语句,并输入映射实例作为参数。

synchronizedMap在Collections中的实现如下所示

   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对象的主要方法添加一个锁。被锁保护的所有方法不能被多个线程同时访问。这意味着像put和get这样的普通操作可以由一个线程同时对Map对象中的所有数据执行。

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

ConcurrentMap在实现中要复杂得多,我们可以参考构建一个更好的HashMap来了解详细信息。简而言之,它的实现同时考虑了线程安全和性能。

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

关于锁定机构: Hashtable锁定对象,而ConcurrentHashMap只锁定桶。