Java集合(六一): ConcurrentHashMap问题汇总

x33g5p2x  于2021-09-25 转载在 Java  
字(1.9k)|赞(0)|评价(0)|浏览(456)

1、jdk1.7升级到1.8,ConcurrentHashMap的变化?

  • 锁方面: 由分段锁(Segment继承自ReentrantLock)升级为 CAS+synchronized实现;
  • **数据结构层面:**将Segment变为了Node,减小了锁粒度,使每个Node独立,由原来默认的并发度16变成了每个Node都独立,提高了并发度;
  • hash冲突: 1.7中发生hash冲突采用链表存储,1.8中先使用链表存储,后面满足条件后会转换为红黑树来优化查询;
  • 查询复杂度: jdk1.7中链表查询复杂度为O(N),jdk1.8中红黑树优化为O(logN));

2、CAS性能很高,为啥jdk1.8升级之后反而多了synchronized?

synchronized之前一直都是重量级的锁,性能差,但是后来java官方(jdk1.5还是1.6)对它进行了优化:针对 synchronized 获取锁的方式,JVM 使用了锁升级的优化方式,就是先使用偏向锁优先同一线程,然后再次获取锁,如果失败,就升级为 CAS 轻量级锁,如果失败就会短暂自旋,防止线程被系统挂起。最后如果以上都失败就升级为重量级锁。所以是一步步升级上去的,最初也是通过很多轻量级的方式锁定的(偏向锁–>CAS轻量级锁–>自旋–>重量级锁)。Java并发基础(四):Synchronized原理和锁优化升级过程_mingyuli的博客-CSDN博客

3、Hashmap中链表大小超8个自动转为红黑树,当删除小于六时重新变为链表,为啥呢?

  • 默认的是链表结构,并不是一开始就是红黑树结构,因为链表比红黑数占用的空间较少;
  • hash冲突导致链表长度真正达到8的概率极小,约为一千万分之一,同时也考虑到红黑树查询比链表快;

根据泊松分布,在负载因子默认为0.75的时候,单个hash槽内元素个数为8的概率小于百万分之一,所以将7作为一个分水岭,等于7的时候不转换,大于等于8的时候才进行转换,小于等于6的时候就化为链表。

4、为什么concurrentHashMap、Hashtable等不允许key/value为null呢?而HashMap可以呢?

原因1:这是因为ConcurrentHashMap和Hashtable都是支持并发的,这样会有一个问题,当你通过get(k)获取对应的value时,如果获取到的是null时,你无法判断,它是put(k,v)的时候value就为null,还是这个key从来没有做过映射。
而HashMap是非并发的,可以通过contains(key)来进行校验。而支持并发的Map在调用m.contains(key)和m.get(key)时m可能已经不同了

 2、什么是快速失败机制(fail-fast)呢?

答:在用迭代器遍历一个集合对象时,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出 Concurrent Modification Exception。

2.1快速失败机制(fail-fast)的原理是啥?

答:迭代器在遍历时直接访问集合中的内容,在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。 每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出Concurrent Modification Exception异常,终止遍历。 (注意:不能依赖于这个异常是否抛出而进行并发操作的判断,因为会有类似CAS操作的“ABA”问题,所以这个异常只建议用于检测并发修改的bug)。

3、什么是安全失败机制(fail—safe)呢?

答:采用安全失败机制的集合容器,在遍历时不是直接在集合上访问的,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于循环的时候是对原集合的“复制品”进行遍历,所以在你遍历的过程中对原集合的更改都不会被迭代器检测到,所以不会报错。

4、快速失败机制(fail-fast)和安全失败机制(fail—safe)的适用场所?

java.util包下的集合类都是快速失败(fail-fast)的,不能在多线程下发生并发修改(迭代过程中被修改)算是一种安全机制,但是预防不了ABA问题;
java.util.concurrent包下的容器都是安全失败(fail—safe),可以在多线程下并发使用,并发修改,不过要想适用于并发场景,需要额外的控制管理;
 

相关文章