ConcurrentHashMap
ConcurrentHashMap使用什么技术来保证线程安全?
大部分操作和HashMap是相同的,例如初始化,扩容和链表向红黑树的转变等。
jdk1.7:Segment+synchronized+HashEntry+Unsafe来进行实现的;
jdk1.8:放弃了Segment臃肿的设计,采用Node+CAS+Synchronized+Unsafe来保证线程安全。
ConcurrentHashMap为什么用了CAS还要用synchronized?
ConcurrentHashMap使用这两种手段来保证线程安全主要是一种权衡的考虑,在某些操作中使用synchronized和CAS,主要是根据锁竞争程度来判断的。
比如:在putVal中,如果计算出来的hash槽没有存放元素,那么就可以直接使用CAS来进行设置值,这是因为在设置元素的时候,因为hash值经过了各种扰动后,造成hash碰撞的几率较低,那么我们可以预测使用较少的自旋来完成具体的hash落槽操作。
当发生了hash碰撞的时候说明容量不够用了或者已经有大量线程访问了,因此这时候使用synchronized来处理hash碰撞比cas效率要高,因为发生了hash碰撞大概率来说是线程竞争比较强烈。
ConcurrentHashMap的get方法是否要加锁,为什么?
不需要,get方法采用了unsafe也就是CAS机制来保证获取到的元素是最新的元素,即采用了volatile来保证集合可见,来保证线程安全。
ConcurrentHashMap迭代器是强一致性还是弱一致性?HashMap呢?
弱一致性(fail-safe),hashmap强一致性(fail-fast)。
ConcurrentHashMap可以支持在迭代过程中,向map添加新元素,而HashMap则抛出了ConcurrentModificationException,因为HashMap包含一个修改计数器,当你调用他的next()方法来获取下一个元素时,迭代器将会用到这个计数器。
ConcurrentHashMap1.7和1.8的区别?
jdk1.8的实现降低锁的粒度,jdk1.7锁的粒度是基于Segment的,包含多个HashEntry,而jdk1.8锁的粒度就是Node。
数据结构:jdk1.7 Segment+HashEntry;jdk1.8数组+链表+红黑树+CAS+synchronized
ConcurrentHashMap和HashMap最大容量的不一致
HashMap最大容量只能达到Intger.MAX_VALUE=2^30超过了就不再扩容而ConcurrentHashMap额外提供了mappingCount()方法最大可以达到2^63-1。
ConcurrentHashMap和HashMap转换为红黑树的条件
HashMap当某个槽的链表长度大于8就会转换为红黑树
ConcurrentHashMap当某个槽个数增加到超过了8且table大于等于64才会转换为红黑树,当table容量小于64的时候,只会扩容,不会转换为红黑树。
取决于:MIN_TREEIFY_CAPACITY 值
ConcurrentHashMap size()方法
ConcurrentHashMap只能返回一个大概数量无法做到100%精确的,因为统计过的槽在size()返回最终结果可能会出现了变化,如果只是为了统计而阻塞增删有点得不偿失。
JAVA8的 ConcurrentHashMap 为什么放弃了分段锁?
jdk8 放弃了分段锁而是用了Node锁,减低锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。
通过 JDK 的源码和官方文档看来, 他们认为的弃用分段锁的原因由以下几点:
- 加入多个分段锁浪费内存空间。
- 生产环境中, map 在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。
- 为了提高 GC 的效率。
但是ConcurrentHashMap的一些操作使用了synchronized锁,而不是ReentrantLock,虽然说jdk8的synchronized的性能进行了优化,但是我觉得还是使用ReentrantLock锁能更多的提高性能。
为什么是synchronized,而不是ReentranLock
- 减少内存开销
假设使用可重入锁来获得同步支持,那么每个节点都需要通过继承AQS来获得同步支持。但并不是每个节点都需要获得同步支持的,只有链表的头节点(红黑树的根节点)需要同步,这无疑带来了巨大内存浪费。
- 获得JVM的支持
可重入锁毕竟是API这个级别的,后续的性能优化空间很小。
synchronized则是JVM直接支持的,JVM能够在运行时做出相应的优化措施:锁粗化、锁消除、锁自旋等等。这就使得synchronized能够随着JDK版本的升级而不改动代码的前提下获得性能上的提升。