美文网首页
ConcurrentHashMap常问问题

ConcurrentHashMap常问问题

作者: 坤坤坤坤杨 | 来源:发表于2022-04-06 17:22 被阅读0次

    1. ConcurrentHashMap的原理

    1.1 JDK1.7的ConcurrentHashMap

    采用了分段锁的思想,将哈希桶数组分成一个个的Segment数组(继承ReentrantLock),每一个Segment里面又有多个HashEntry,也是被volatile修饰的,是为了保证在数组扩容时候的可见性,HashEntry中又有key,hash,value,next属性,而value,next又是被volatile修饰为了保证多线程环境下数据修改时的可见性,多线程环境下ConcurrentHashMap会对这些小的数组进行加锁,这样多线程操作Map就相当于是操作单线程环境下的HashMap,比如A线程对其中一个段进行写操作的时候线程B就不能对其进行写操作,但是线程B可以对其他的段进行写操作,从而实现并发修改和访问。

    1.2 JDK1.8的ConcurrentHashMap

    JDK1.8的ConcurrentHashMap摒弃了分段锁的思想,采用jdk1.8中HashMap的底层机构,Node数组+链表+红黑树。Node是继承了Entry的一个内部类,他的value和next都是被volatile修饰的原因也是为了保证多线程下修改数据的可见性。

    采用CAS+synchronized实现更加细粒度的锁,将锁的级别控制在更细粒度的哈希桶数组元素的级别,只需要锁住链表头节点(红黑树的根节点)就不会影响到其他哈希桶数组元素的读写,大大的提高了并发度。

    2. ConcurrentHashMap的get方法需要加锁吗?

    是不需要加锁的,因为Node节点使用了volatile修饰了value和next节点,而在jdk8中同样也是使用了volatile修饰了value和next节点,这样保证可见性了就不需要加锁了。

    1.3 1.7和1.8中ConcurrentHashMap的区别

    1. 数据结构:1.7中底层是使用Segment数组+链表的结构,1.8中采用了Node数组+链表+红黑树的结构。
    2. 查询复杂度:1.7遍历链表为O(N)而1.8变成红黑树后的复杂度为O(logN)。
    3. 保证线程安全的机制:1.7采用分段锁机制实现线程安全,其中Segment继承自ReentrantLock。1.8采用了CAS+synchronized保证线程安全,因此也引出了锁粒度上的区别。
    4. 锁粒度:1.7是对需要进行数据操作的Segment加锁,1.8是调整为每个数组元素的头节点加锁。

    3. ConcurrentHashMap不支持key或者value为空的原因?

    key不能为空,无法解释,没有什么可说的,可能就是作者的想法。
    value不能为空是因为ConcurrentHashMap是工作在多线程环境下的,如果调用get方法,返回null,这个时候就存在二义性,因为ConcurrentHashMap不知道是没有这个key,还是这个key对应的值是不是null。所以干脆不支持value为null。

    4. ConcurrentHashMap的迭代器是强一致性还是弱一致性?

    HashMap的迭代器是强一致性的,而ConcurrentHashMap的迭代器是弱一致性的,因为在多线程环境下,在创建迭代器的过程中,内部的元素会发生变化,如果是在已经遍历过去的数据中发生变化,迭代器是无法反映出来数据发生了改变,如果是发生在未迭代的数据时,这个时候就会反映出来,强一致性就是说只要迭代器创建出来之后数据就不会发生改变了。这样设计的好处就是迭代器线程可以使用原来的老数据进行遍历,写线程可以并发的完成改变,这样就保证了多个线程执行的时候的连续性和可拓展性,提升了并发性能。

    5. JDK1.8中为什么使用synchronized替换ReentrantLock?

    1. 性能:因为是synchronized在jdk1.6引入了大量的优化,如锁升级,锁粗化,锁消除,自旋锁,自适应自旋锁等的优化,本身的性能得到了提升。
    2. 并发度和内存开销:CAS+synchronized方式时加锁的对象是每个链条的头节点,相对Segment再次提高了并发度,如果使用可重入锁达到同样的效果的话,则需要大量继承自ReentrantLock的对象,造成巨大的内存浪费。

    6. ConcurrentHashMap的并发度是如何设计的?

    并发度可以理解为程序运行时能够同时更新ConcurrentHashMap且不产生锁竞争的最大线程数。

    JDK1.7中,并发度就是ConcurrentHashMap中的分段个数,即Segment[]数组的长度,默认是16,这个值可以在构造函数中设置。如果自己设置了并发度那么就会和HasHMap一样会去找到大于等于当前输入值的最小的2的幂指数作为实际并发度。如果过小就会产生锁竞争,如果过大,那么就会导致本来位于同一个Segment的的访问会扩散到不同的Segment中,导致性能下降。
    JDK1.8中摈弃了Segment的概念,选择使用HashMap的结构,并发度依赖于数组的大小。

    7. ConcurrentHashMap和hashTable的效率哪一个高?为什么?

    ConcurrentHashMap效率高,因为hashTable是给整个hash表加锁,而ConcurrentHashMap锁粒度要更低。

    8. 还有什么其他方式可以在多线程情况下操作Map?

    使用Collections.synchronizedMap(Map类型的对象)方法进行同步加锁,把对象转换为SynchronizedMap<K,V>类型。其实就是对HashMap做了一次封装,多个线程竞争一个mutex对象锁,在竞争激烈的情况下性能也非常差,不推荐使用。

    相关文章

      网友评论

          本文标题:ConcurrentHashMap常问问题

          本文链接:https://www.haomeiwen.com/subject/wtmrsrtx.html