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

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

哈希表 collections . synchronizedmap(地图) ConcurrentHashMap

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

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


当前回答

以下是一些例子:

1) ConcurrentHashMap只锁定Map的一部分,而SynchronizedMap锁定整个Map。 2) ConcurrentHashMap比SynchronizedMap性能更好,扩展性更强。 3)在多读取器和单写入器的情况下,ConcurrentHashMap是最好的选择。

此文本来自Java中的ConcurrentHashMap和哈希表之间的差异

其他回答

你对哈希表的看法是对的,你可以忘了它。

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

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

但是,不要认为ConcurrentHashMap是具有典型同步块的HashMap的简单替代方案。阅读这篇文章可以更好地理解其复杂性。

synchronizedmap()方法同步HashMap的所有方法,并有效地将其简化为每次只能进入一个线程的数据结构,因为它将每个方法锁定在一个公共锁上。

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

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

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

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

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

两者之间的主要区别是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。