HashMap有哪些线程安全的处理方式(面试题)
- 概念
- 1. synchronizedMap(Map<K,V> m)
- 2. ConcurrentHashMap
- ConcurrentHashMap Jdk1.7 vs Jdk1.8
概念
基于哈希表的 Map 接口的实现。此实现提供所有可选的映射操作,并允许使用 null 值和 null 键。(除了非同步和允许使用 null
之外,HashMap 类与 Hashtable 大致相同。)此类不保证映射的顺序,特别是它不保证该顺序恒久不变。
此实现假定哈希函数将元素适当地分布在各桶之间,可为基本操作(get 和 put)提供稳定的性能。
1. synchronizedMap(Map<K,V> m)
public static <K,V> Map<K,V> synchronizedMap(Map<K,V> m) {
return new SynchronizedMap<>(m);
}
在Java.utils.Collections类中,提供了synchronizedMap静态方法,返回一个新的线程安全的Map实例。其实不光Map,Collection、List、Set的所有子类也支持此方式来提供线程安全的容器。
通过源码不难发现,在Collections类中定义了静态内部类,以Map为例,synchronizedMap返回的其实是java.util.Collections.SynchronizedMap类型,其所有原Map定义的方法均添加了synchronized关键字,这个做法很HashTable比较类似。在保证线程安全的同时,性能下降明显。
2. ConcurrentHashMap
java.util.concurrent.ConcurrentHashMap为HashMap的线程安全版,将原Map拆分成多个独立的Segment(通过继承ReentrantLock来进行加锁),通过锁住独立的Segment来降低锁的粒度,在高并发的情况下尽可能减少锁冲突。这样的设计需要二次定位,即先对key对应的Segment定位,再定位hash值对应的数据下标。在JDK1.8后,对ConcurrentHashMap锁机制进行优化,采用Node + CAS + synchronized,摒弃了Segment的概念,进一步缩小了锁的粒度。
下面简易对比一下ConsurrentHashMap在JDK1.7和1.8中的差异
ConcurrentHashMap Jdk1.7 vs Jdk1.8
jdk版本 | 1.7 | 1.8 |
---|---|---|
底层实现 | 数组+链表 | 数组+链表 / 红黑树 |
数据结构 | Segment 数组 + HashEntry 节点 | Node 节点 |
锁 | 分段锁,默认并发是16,一旦初始化,Segment 数组大小就固定,后面不能扩容 | CAS + synchronized 来保证并发安全性 |
put 操作 | 先获取锁,根据 key 的 hash 值 定位到 Segment ,再根据 key 的 hash 值 找到具体的 HashEntry ,再进行插入或覆盖,最后释放锁 | 根据 key 的 hash 值 定位到 Node节点,再判断首节点是否为空,空的话通过 cas 去赋值首节点 ; 首节点非空的话,会用 synchronized 去锁住首节点,并判断是是同个首节点,是的话再去操作 |
释放锁 | 需要显示调用 unlock() | 不需要 |