Hashtable和Collections.synchronizedMap(HashMap)的区别

53
据我所知,java.util.Hashtablejava.util.Map 接口的每个方法都进行了同步,而 Collections.synchronizedMap(hash_map) 返回一个包含同步方法的包装对象,将调用委托给实际的 hash_map(如果我说错了,请纠正我)。 我有两个问题:
  1. 同步每个方法和使用包装类会产生什么区别?在什么情况下选择其中之一?

  2. 当我们执行 Collections.synchronizedMap(hash_table) 时会发生什么?这是否等同于使用普通的 java.util.Hashtable


23
这不是那个问题的复制。这是在比较对HashMap(或HashTable)进行同步封装后的对象和一个HashTable对象。 - user166390
1
我很想知道是否有性能上的差异,而目前的回答都没有涉及到这一点。通过类型为Map的变量调用Collections.synchronizedMap的方法意味着在进入实现之前有2个接口虚拟调用,而使用Hashtable最多只有1个虚拟调用,你可以直接将变量声明为Hashtable类型,并且不需要进行虚拟调用。但也许HashMap在其他方面有所不同,使其整体更快。 - Boann
6个回答

75

我可以发现两个类的实现之间还有一个不同之处:

Hashtable 类中的所有方法都是同步的,即在方法级别进行锁定,因此可以说互斥体始终在 Hashtable 对象 (this) 级别上。

• 方法 Collections.synchronizedMap(Map) 返回一个 SynchronizedMap 实例,它是 Collections 类中的一个内部类。该类在一个互斥体的 Synchronized 块中拥有所有的方法。这里的区别在于互斥体。内部类 SynchronizedMap 有两个构造函数,一个只接受 Map 作为参数,另一个则接受一个 Map 和一个 Object (互斥体) 作为参数。默认情况下,如果使用仅传递 Map 的第一个构造函数,则会将 this 用作互斥体。虽然开发者允许通过第二个参数传递另一个互斥体对象,以使对 Map 方法的锁定仅限于该 Object,因此比 Hashtable 更灵活。

• 因此,Hashtable 使用方法级别的同步,而 Collections.synchronizedMap(Map) 提供了对提供的互斥体进行锁定的灵活性。


6
多么美妙的答案,强调了其他回答中未被触及的内容。+1 - Raúl
3
额外的构造函数和块级别的同步(与HashTable不同的方法级别的同步)是关键。 - Raúl
8
这是非常有趣的信息,但是内部的SynchronizedMap类是私有的,而且没有静态方法与接受互斥对象作为参数版本的构造函数对应,所以开发人员实际上不能提供自定义的互斥锁,对吧? - CarLuva
@CarLuva 你是对的。我没有看到任何公开使用第二个构造函数的方法。 - mankadnandan
https://stackoverflow.com/questions/50829463/purpose-of-overloaded-constructor-in-synchronizedmap-class/50829585#50829585 - Somnath Musib
显示剩余2条评论

17

通过一些(希望正确的)研究,我得到了以下答案:

  1. 两者都提供相同程度的同步。如果你通过Collections.synchronized将Hashtable包装起来,你会有相同的同步程度,但是多了一层不必要的同步。

  2. HashtableCollections.synchronizedMap(HashMap)的主要区别在于API级别上存在差异。因为Hashtable是Java的遗留代码的一部分,所以你会发现Hashtable API已经增强以实现Map接口,成为了Java集合框架的一部分。这意味着,如果你通过Collections.synchronizedMap()来包装Hashtable,被包装的Hashtable的API就会被限制为Map的API。因此,如果你的行为定义涵盖了Hashtable的API,则显然会被改变/限制。


此外,Hashtable 方法是同步的,而 Synchronized map 的方法实现在互斥锁上有同步映射。 - Gaurav

4
Java类库中出现的第一个关联集合类是Hashtable,它是JDK 1.0的一部分。Hashtable提供了易于使用、线程安全的关联映射功能,非常方便。然而,线程安全是有代价的——Hashtable的所有方法都是同步的。在那个时候,无争用同步会带来可衡量的性能损耗。Hashtable的继任者HashMap出现在JDK 1.2的Collections框架中,通过提供一个非同步的基类和一个同步的包装器Collections.synchronizedMap来解决了线程安全问题。将基本功能与线程安全的Collections.synchronizedMap分离开来,使得需要同步的用户可以使用它,而不需要同步的用户则不必为此付费。
Hashtable和synchronizedMap采用的简单同步方法——在Hashtable或同步Map包装对象上同步每个方法——有两个主要缺点。首先,它阻碍了可扩展性,因为一次只能有一个线程访问哈希表。同时,它也不能提供真正的线程安全性,因为许多常见的复合操作仍然需要额外的同步。虽然像get()和put()这样的简单操作可以在没有额外同步的情况下安全完成,但是有几种常见的操作序列,比如迭代或put-if-absent,仍然需要外部同步来避免数据竞争。
以下链接是源链接,包含更多信息:Concurrent Collections Classes

1
第二段说HashtablesynchronizedMap是一样的东西。这意味着我们根本没有回答问题:它们的区别是什么? - Pacerier

4

另一个需要注意的不同之处是HashTable不允许空键或值,而HashMap允许一个空键和任意数量的空值。由于synchronizedMap是HashMap的包装器,因此它在处理空键和值方面的行为与HashMap相同。


2
无关的回答 - Mr.Q
2
这直接回答了标题中提出的问题,这使得它非常相关。即使问题正文提出了更具体的一组问题,这也是正确的。请记住,人们通常根据标题找到并阅读问题,而这个答案对于那些人来说是有生产力的。如果失去这个答案,将会降低这个问题的总体实用性。 - AndrewF

3
区别不仅在于显而易见的API层面,还有许多实现层面上的微妙差别。例如,Hashtable没有像HashMap那样提供高级重新计算提供的键的哈希码以减少哈希冲突。另一方面,Hashtable#hashCode()避免了自我引用哈希表的无限递归,以允许“某些1.1时代的小程序与自我引用哈希表一起工作”。
总的来说,但是,除了基本的正确性和向后兼容性之外,人们不应指望Hashtable获得任何进一步的改进或完善。它被认为是深深Java过去的遗物。

这个问题不是关于比较hashtable和hashmap。 - Mr.Q
是的,它是:synchronizedMap(new HashMap<>()) vs. new Hashtable<>() - Marko Topolnik

2
冒昧地说(或者可能是错误的),区别在于同步包装器会为任意集合添加自动同步(线程安全)(参见此链接)。并且继续说道,以这种方式创建的集合与通常同步的集合(如Vector)一样线程安全。

您可能想查看此线程以了解HashMap和并发问题:HashMap并发问题(或者您可能已经非常清楚)。一个很好的例子是:

您描述的条件将无法满足HashMap。由于更新映射的过程不是原子性的,因此您可能会遇到处于无效状态的映射。多次写入可能会使其处于损坏状态。ConcurrentHashMap(1.5或更高版本)可以实现您所需的功能。(参见此链接)

我认为,在需要并发性的情况下,我倾向于使用同步集合,否则您可能会为自己创造更多工作(请参见下文)。在改变行为方面:

如果使用显式迭代器,则必须在同步块内调用迭代器方法。不遵循此建议可能会导致不确定性行为。

提供的(Oracle)链接中提供了使用同步的更多后果。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接