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添加独占锁,这个添加的过程是用tryLock()循环进行的,循环有两个出口,一个是成功初始化,另一个就是循环次数超过了一个阀值.然后使用lock()加锁阻塞线程直到加锁成功.
    • 然后说一下扩容的问题,虽然前面说了一个segment内部就是一个HashMap,但是还是有很多不同的,在扩容方面因为要考虑并发的情况.
    • 并发问题
      • 并发问题主要是讨论使用get操作时,同时还有put或remove操作和扩容,因为put和remove都是有独占锁的,但是get是无锁的.
      • get时使用了put操作:
        • 先get操作,如果是先进行的get操作,这是get正在链表上寻找值,而新插入的值是插到链表表头,所以没有影响,
        • 先put操作,需要保证插入的值也被扫描到,具体有unsafe.putOrderedObject方法,这个方法保证可见性
      • get时发生扩容
        • 因为底层数组table是volatile修饰的,所以如果是先put导致扩容,有volatile保证可见性,如果get先行,就是在原来的数组上扫描.
  • 1.8

    • 1.8我不会说太多,不是因为和1.7差不多,而恰恰相反,1.8和1.7差别是很大的,一开始我很难理解,到现在其实还是不太理解,只能简单说说,等多看几遍,再补充
    • 1.8中似乎是没有分段锁的概念了,至少在我看来是这样的,虽然还是有segment这个内部类,但是其内部只有一个负载因子的属性,其次就是整个ConcurrentHashMap使用的是一个数组,数组元素是一个链表或者红黑树,和HashMap是很相似的,但就是在这样的底层数据结构上,还是实现了线程安全.
    • 1.8中维护了一个属性—sizeCtl,这是一个状态属性,它的值代表了底层数组的在多线程情况下的状态,这个值的改变都是通过CAS操作来完成的,
    • 1.8中的锁粒度更小了,从putVal方法来看,先通过CAS尝试插入值,如果失败就代表有并发情况,然后是通过同步锁,锁是通过synchronized来实现的,但锁住的只是数组中的链表头结点对象,这样锁粒度很小.是很好的一个实现.
    • 和HashMap中不同的是,ConcurrentHashMap不允许key和value为null.会抛出空指针异常.
    • 扩容问题:因为底层只有一个数组,所以扩容就只是一个任务,但是这个任务不会由一个线程来完成,因为任务太大,所以会为每个线程分配一部分数组的复制迁移工作,至于的具体的,还没有很看懂.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值