Hashtable,Collections.SynchronizedMap和ConcurrentHashMap线程安全实现原理的区别以及性能测试
这三种 Map 都是 Java 中比较重要的集合类,虽然前两个不太常用,但是因为与多线程相关,所以关于这几种 Map 的对比已经成为了 Java 面试时的高频考点。首先要说明的是,其中每一个单独拎出来都足够支撑一篇长篇大论的技术文章,所以本文把重点放在了这三种集合类的线程安全实现原理的对比以及性能测试上,其他细节不做深入探讨。
文章目录
一、线程安全原理对比
1. Hashtable
首先必须吐槽一下这个类名,作为官方工具类竟然不符合驼峰命名规则,怪不得被弃用了,开玩笑哈哈,主要原因还是性能低下,那 Hashtable 的性能为什么低下呢,这个嘛只需要看一下它的源码就一目了然了,以下是 Hashtable 中几个比较重要的方法:
public synchronized V put(K key, V value) {
...}
public synchronized V get(Object key) {
...}
public synchronized int size() {
...}
public synchronized boolean remove(Object key, Object value) {
...}
public synchronized boolean contains(Object value) {
...}
... ...
查看源码后可以看出,Hashtable 实现线程安全的原理相当简单粗暴,直接在方法声明上使用 synchronized 关键字。这样一来,不管线程执行哪个方法,即便只是读取数据,都需要锁住整个 Hashtable 对象,可想而知其并发性能必然不会太好。
2. Collections.SynchronizedMap
SynchronizedMap 是 Collections 集合类的私有静态内部类,其定义和构造方法如下:
private static class SynchronizedMap<K,V> implements Map<K,V>, Serializable {
private static final long serialVersionUID = 1978198479659022715L;
// 用于接收传入的Map对象,也是类方法操作的对象
private final Map<K,V> m;
// 锁对象
final Object mutex;
// 以下是SynchronizedMap的两个构造方法
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;
}
}
-
SynchronizedMap 一共有三个成员变量,序列化ID抛开不谈,另外两个分别是 Map 类型的实例变量 m,用于接收构造方法中传入的 Map 参数,以及 Object 类型的实例变量 mutex,作为锁对象使用。
-
再来看构造方法,SynchronizedMap 有两个构造方法。第一个构造方法需要传入一个 Map 类型的参数,这个参数会被传递给成员变量 m,接下来 SynchronizedMap 所有方法的操作都是针对 m 的操作,需要注意的是这个参数不能为空,否则会由 Objects 类的 requireNonNull() 方法抛出空指针异常,然后当前的 SynchronizedMap 对象 this 会被传递给 mutex 作为锁对象;第二个构造方法有两个参数,第一个 Map 类型的参数会被传递给成员变量 m,第二个 Object 类型的参数会被传递给 mutex 作为锁对象。
-
最后来看 SynchronizedMap 的部分主要方法:
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 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);} }
从源码可以看出,SynchronizedMap 实现线程安全的方法也是比较简单的,所有方法都是先对锁对象 mutex 上锁,然后再直接调用 Map 类型成员变量 m 的相关方法。这样一来,线程在执行方法时,只有先获得了 mutex 的锁才能对 m 进行操作。因此,跟 Hashtable 一样,在同一个时间点,只能有一个线程对 SynchronizedMap 对象进行操作,虽然保证了线程安全,却导致了性能低下。这么看来,连 Hashtable 都被弃用了,那性能同样低下的 SynchronizedMap 还有什么存在的必要呢?别忘了,后者的构造方法需要传入一个 Map 类型的参数,也就是说它可以将非线程安全的 Map 转化为线程安全的 Map,而这正是其存在的意义,以下是 SynchronizedMap 的用法示例 (这里并没有演示多线程操作):
Map<String