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

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

哈希表 collections . synchronizedmap(地图) ConcurrentHashMap

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

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


当前回答

根据您的需要,使用ConcurrentHashMap。它允许从多个线程并发修改Map,而不需要阻塞它们。synchronizedmap (map)创建了一个阻塞map,这会降低性能,但确保了一致性(如果使用得当)。

如果您需要确保数据的一致性,并且每个线程需要有一个最新的映射视图,则使用第二个选项。如果性能非常关键,并且每个线程只向映射中插入数据,读取频率较低,则使用第一种方法。

其他回答

除了建议之外,我还想发布与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来了解详细信息。简而言之,它的实现同时考虑了线程安全和性能。

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

ConcurrentHashMap是如何实现的

ConcurrentHashMap was developed as alternative of Hashtable and support all functionality of Hashtable with additional ability, so called concurrency level. ConcurrentHashMap allows multiple readers to read simultaneously without using blocks. It becomes possible by separating Map to different parts and blocking only part of Map in updates. By default, concurrency level is 16, so Map is spitted to 16 parts and each part is managed by separated block. It means, that 16 threads can work with Map simultaneously, if they work with different parts of Map. It makes ConcurrentHashMap hight productive, and not to down thread-safety.

如果你对ConcurrentHashMap的一些重要特性感兴趣,以及什么时候应该使用这种Map的实现——我只是放了一个链接到一篇好文章——如何在Java中使用ConcurrentHashMap

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

当您有简单的插入和查找时,这不是什么大问题(除非您做得非常密集),但是当您需要遍历整个Map时,这就变成了一个大问题,对于一个大型Map来说,这可能会花费很长时间——当一个线程这样做时,所有其他线程都必须等待,如果它们想要插入或查找任何东西。

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

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

╔═══════════════╦═══════════════════╦═══════════════════╦═════════════════════╗
║   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只锁定桶。