本文参考自:Hashtable, Collections.SynchronizedMap和ConcurrentHashMap线程安全实现原理的区别以及性能测试_IamHYN的博客-CSDN博客
Java集合常见面试题总结(下) | JavaGuide(Java面试 + 学习指南)
Hashtable
底层数据结构和JDK1.8之前的HashMap一样,是数组+链表。确保线程安全就是在每个方法前加synchronized关键字,即使只是读取数据也会用锁锁住整个对象,并发性很差。
Collections.SynchronizedMap
SynchronizedMap 是 Collections 集合类的私有静态内部类,主要用于将不安全的map包装成安全的map。SynchronizedMap 成员变量包含一个Map类型m用来接收传入的Map对象,一个Object类型的mutex用来充当锁。实现线程安全的方法就是先对mutex上锁,然后执行m的相关方法。和Hashtable一样,同一时刻只能有一个线程调用其方法,并发性能差。
SynchronizedMap成员变量及其构造函数:
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;
}
}
ConcurrentHashMap
JDK1.8之前
底层数据结构:ConcurrentHashMap是由Segment数组结构和HashEntry数组结构组成。Segment扮演锁的角色,而且其继承了ReentrantLock,所以是一种可重入锁。HashEntry用于存储键值对数据,每个HashEntry数组属于链表结构。
线程安全实现方式:ConcurrentHashMap采用分段锁,就是对每个Segment数组元素加锁,Segment的个数一旦初始化就不能改变,默认大小是16,也就是说默认可以同时支持16个线程并发写。
JDK1.8及以后
底层数据结构:跟HashMap JDK1.8的HashMap结构类似,采用数组+链表/红黑二叉树,链表长度大于8时转换成红黑树。
线程安全实现方式:采用Node+CAS+synchronized+volatile来保证并发安全。value用volatile关键字修饰,读取靠volatile保证线程安全。添加或修改时用synchronized关键字锁定当前链表或红黑二叉树的首节点,保证线程安全,此外还用到大量的CAS操作。JDK.8中对Node元素加锁,锁粒度更细,并发度更高,而且只要不发生哈希冲突,就不会影响其他Node的读写。