面试准备 --- ConcurrentHashMap

这里需要说明一下,本人才疏学浅,写下这些东西,完全是暂时记下自己的一些理解,无法保证理解的正确性,想要学习ConcurrentHashMap的还是不要看.

注意点

  • 为什么使用ConcurrentHashMap,是因为需要在一个线程安全的映射集合类,那为什么不使用HashTable呢?其实就是我在HashTable的博客中说到的,HashTable的锁粒度实在太大,虽然保证了线程安全,但是并发性能无法保证,那么ConcurrentHashMap是如何实现线程安全的呢,是通过分段锁来实现

  • 因为1.7和1.8在HashMap和ConcurrentHashMap的实现上还是不同的,这里需要分开说:

  • 1.7

    • 首先,ConcurrentHashMap的最底层是一个segment数组,segment是一个 内部类,它继承了ReentrantLock这个类,这个类是可重入锁,重入锁允许同一个线程可以重复进入,也就是这个锁,实现了ConcurrentHashMap中的分段锁,每一个segment就是一段,其内部实现其实很像一个hashMap.
    • ConcurrentHashMap初始化的参数:
      • segment数组的默认大小是16,也就是并行度,最多允许多少个线程同时访问,segment数组初始化以后就不能扩容了,但是每个segment内部的数组是可以扩容的.
      • segment分为了16个,而每一个segment的初始大小时2,阀值就是2*0.75=1.5(这个阀值代表每一个segment填入第二个键值对时就需要扩容了),初始化的时候只会初始化第一个segment,也就是说segments[0],其余的segment还都是null,等使用的时候才进行初始化.
    • 上面也说到了,每一个segment内部是一个HashMap,而我们无论是查询,添加,删除,很多方面都是类似HashMap,不同的是需要先选择使用哪一个segment,然后在segment内部再次进行hash定位,找到对应的位置,有意思的是,ConcurrentHashMap维护了两个变量segmentShiftsegmentMask,具体是怎么得到的不说了,这两个变量主要用于计算机快速定位segment数组中的位置.
    • segment初始化的问题,前面说到过,实例化map时只会初始化第一个segment,其他的segment只会在第一次使用时初始化,而如果有多个线程同时触发一个segment的初始化怎么办呢?这里用到的是一个CAS,熟悉并发的肯定对CAS很了解,主要就是在循环中不断检查和初始化,如果本线程初始化成功或者其他线程初始化成功就退出循环.
    • 说一下加锁的问题,在put方法内部,定位到一个segment后,就会为这个segment添加独占锁,这个添加的过程是循环进行的,循环有两个出口,一个是成功初始化,另一个就是循环次数超过了一个阀值.
    • 然后说一下扩容的问题,虽然前面说了一个segment内部就是一个HashMap,但是还是有很多不同的,在扩容方面因为要考虑并发的情况,要复杂的多.
  • 1.8

    • 1.8我不会说太多,不是因为和1.7差不多,而恰恰相反,1.8和1.7差别是很大的,一开始我很难理解,到现在其实还是不太理解,只能简单说说,等多看几遍,再补充
    • 1.8中似乎是没有分段锁的概念了,至少在我看来是这样的,虽然还是有segment这个内部类,但是其内部只有一个负载因子的属性,其次就是整个ConcurrentHashMap使用的是一个数组,数组元素是一个链表或者红黑树,和HashMap是很相似的,但就是在这样的底层数据结构上,还是实现了线程安全.
    • 1.8中维护了一个属性—sizeCtl,这是一个状态属性,它的值代表了底层数组的在多线程情况下的状态,
    • 1.8中的锁粒度更小了,从putVal方法来看,锁是通过synchronized来实现的,但锁住的只是数组中的一个位置,这是很好的一个实现.
    • 和HashMap中不同的是,ConcurrentHashMap不允许key和value为null.会抛出空指针异常.

猜你喜欢

转载自blog.csdn.net/qq_36865108/article/details/86649738