简单总结一下面试中常问的HashMap、HashTable和ConcurrentHashMap的区别。
一、HashMap
是否线程安全: 否。
底层结构:
- jdk1.7时,底层结构为 数组 + 链表。
- jdk1.8时,底层结构为 数组 + 链表 + 红黑树。
红黑树与链表的转换规则:
- 如果链表中节点个数超过8时,链表转化为红黑树。
- 如果红黑树中节点小于6时,红黑树退还为链表。
- 如果哈希桶中某条链表的个数超过8,且桶的个数超过64时会将链表转换为红黑树,否则会扩容。
PS:
计算哈希值的代码中 (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16) 的作用?
static final int hash(Object key) {
int h;
return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}
答: 减少哈希碰撞的概率,使散列更加均匀。
二、HashTable
是否线程安全: 是
效率: HashTable所有的方法都使用synchronized加锁,像一些读操作不存在线程不安全问题它也进行了加锁,这就导致了效率低下,所以HashTable已经被丢弃不再使用了。
三、ConcurrentHashMap
是否线程安全: 是。
jdk1.7:
-
jdk1.7时,底层结构为 segment数组 + 链表。
-
采用技术:锁分段技术,即将数组分成若干个段,然后对这些段进行加锁,这样就可以根据不同的段进行并发操作。
-
继承ReentrantLock实现同步锁机制。
jdk1.8:
- jdk1.8时,底层结构为 Node数组 + 链表 + 红黑树
- 采用技术:将数组中的每个位置都分别加锁,相较于1.7版本将锁更加细化,大大降低了锁冲突的概率。
- CAS+synchronized实现同步锁机制。
jdk1.7与jdk1.8共同点: 它们的读操作都没有加锁,而是使用了volatile关键字,保证了每次读取到的都是内存中的最新值;读写效率都比较高,因为读操作是无锁的,可以并发执行,写操作锁的是Segment/Node,所以对同一个Segment/Node读操作是有锁冲突的,对不同的Segment/Node读操作是没有冲突的。