| ConcurrentHashMap | Hashtable |
底层数据结构 |
- JDK1.7的 ConcurrentHashMap 底层采用 Segment数组 + HashEntry数组 实现;
- JDK1.8 采用的数据结构跟HashMap1.8的结构一样,数组+链表/红黑二叉树
| 直接就是HashMap加上Synchronized |
实现线程安全的方式 |
- 在JDK1.7的时候,ConcurrentHashMap(分段锁) 对整个桶数组进行了分割分段(Segment),每一把锁只锁容器其中一部分数据,多线程访问容器里不同数据段的数据,就不会存在锁竞争,提高并发访问率。(默认分配16个Segment,比Hashtable效率提高16倍。)
- JDK1.8 的时候已经摒弃了Segment的概念,而是直接用Node 数组+链表+红黑树的数据结构来实现,并发控制使用synchronized 和 CAS 来操作。(JDK1.6以后 对 synchronized锁做了很多优化) 整个看起来就像是优化过且线程安全的 HashMap,虽然在JDK1.8中还能看到 Segment 的数据结构,但是已经简化了属性,只是为了兼容旧版本;
| Hashtable(整张表一把锁) :使用 synchronized 来保证线程安全,效率非常低下。当一个线程访问同步方法时,其他线程也访问同步方法,可能会进入阻塞或轮询状态,如使用 put 添加元素,另一个线程不能使用 put 添加元素,也不能使用 get,竞争会越来越激烈效率越低。 |
锁粒度 |
- JDK1.7:每个Segment数组(继承自ReentrantLock)一把锁(默认16个);读操作不加锁,由于HashEntry的value变量是 volatile的,也能保证读取到最新的值;
- JDK1.8:每个Node一把锁;
| 一共一把锁 |
扩容 |
- JDk1.7:段内扩容(段内元素超过该段对应Entry数组长度的75%触发扩容,不会对整个Map进行扩容),插入前检测需不需要扩容,有效避免无效扩容;
- JDK1.8:(参考JavaGuide大神)
- 所在链表的元素个数达到了阈值 8,则会调用treeifyBin 方法把链表转换成红黑树,不过在结构转换之前,会对数组长度进行判断;
- 如果数组长度n小于阈值MIN_TREEIFY_CAPACITY ,默认是64,则会调用tryPresize 方法把数组扩大到原来的两倍,并触发transfer 方法,重新调整节点的位置;
- 新增节点之后,会调用addCount 方法记录元素个数,并检查是否需要进行扩容,当数组元素个数达到阈值时,会触发transfer 方法,重新调整节点的位置;
| 同HashMap的扩容方式,每次2倍,因子是0.75 |