关于HashMap详请见Java·hashmap
本文主要讲讲三者的区别
对比项 | HashMap | ConcurrentHashMap | Hashtable | 说明 |
---|---|---|---|---|
基类 | AbstractMap | AbstractMap | Dictionary | 都实现了Map接口 |
数据结构 | 数组+链表 | 数组+链表 | 数组+链表 | |
存储字段 | Node<K,V>[] table | (volatile)Node<K,V>[] table | Entry<?,?>[] table | 都实现了Map.Entry<K,V>接口 |
初始大小 | 16 | 16 | 11 | 尽量使哈希表的大小为奇数、素数 |
扩容策略 | newsize = oldsize*2 | newCap = oldCap << 1 参考 | newCapacity = (oldCapacity << 1) + 1 | 当哈希表的大小为素数时,简单的取模哈希的结果会更加均匀 |
null键null值 | 允许,键为null的放在table[0]的哈希桶中 | 不允许 | 不允许 | 并发的都不允许参考 |
迭代器 | Iterator(是 fail-fast 迭代器) | enumerator(不是 fail-fast 迭代器) | enumerator迭代key或value,Iterator迭代Entry(有 fail-fast 机制)参考 | |
锁 | CAS自旋赋值+synchronized同步+LockSupport阻塞等手段,效率高 | synchronized同步关键字,锁住整张表,效率低 |
ConcurrentHashMap锁
JDK7里面采用分段锁,最大并发个数就是Segment的个数,默认值是16,这个值就是并发的粒度;
JDK8里面,去掉了分段锁,而采用更细粒度的table元素级别,也就是说只需要锁住这个链表的head节点,并不会影响其他的table元素的读写,好处在于并发的粒度更细,影响更小,从而并发效率更好
ConcurrentHashMap扩容时的读写操作
- (1)对于get读操作,如果当前节点有数据,还没迁移完成,此时不影响读,能够正常进行。 如果当前链表已经迁移完成,那么头节点会被设置成fwd节点(ForwardNode),此时get线程会帮助扩容。
- (2)对于put/remove写操作,如果当前链表已经迁移完成,那么头节点会被设置成fwd节点,此时写线程会帮助扩容,如果扩容没有完成,当前链表的头节点会被锁住,所以写线程会被阻塞,直到扩容完成。
参考链接:
深入理解HashMap+ConcurrentHashMap的扩容策略
Hashtable 的实现原理
HashMap和Hashtable的区别
面试必备:HashMap、Hashtable、ConcurrentHashMap的原理与区别
网友评论