ConcurrentHashMap

Posted 128-cdy

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ConcurrentHashMap相关的知识,希望对你有一定的参考价值。

ConcurrentHashMap与HashMap的实现方法类似,不同的是它采用的是分段锁的思想支持并发的操作,它是线程安全的。

要了解ConcurrentHashMap,首先要了解Segment,一个Segment就相当于一个HashMap对象。与HashMap一样,Segment包含一个HashEntry数组,数组中的每个HashEntry既是一个键值对,也是一个链表的节点。在ConcurrentHashMap中,有2的N次方个Segment对象,他们共同保存在一个segments数组中。

整个ConcurrentHashMap的结构如下:

技术图片

ConcurrentHashMap的并发读写的几种情况:

  1. 不同Segment之间写入是可以并发的
  2. 同一Segment的写和读是可以并发执行的
  3. 同一Segment的写是不可以并发执行的,原因在于Segment的写入时需要上锁的,对于同一个Segment的并发写入就会被阻塞。

 每一个Segment就像一个自治区,读写操作高度自治,Segment之间互不影响。不同的Segment的写入或者读是可以并发执行的,对于同一个Segment的一读一写也是可以并发执行的,但是同一Segment并发写入需要上锁,会被阻塞。都各自持有一把锁,ConcurrentHashMap当中每个Segment都各自持有一把锁,在保证线程安全的同时降低了锁的粒度,度高了并发度。

ConcurrentHashMap的读写过程:

  • Get方法
  1. 通过key做Hash运算,得到hash值;
  2. 通过hash值,定位到对应的Segment对象;
  3. 再次通过hash值,定位到Segment的数组的具体位置。
  • Put方法
  1. 通过key做Hash运算,得到hash值;
  2. 通过hash值,定位到对应的Segment对象;
  3. 获取可重入锁;
  4. 再次通过hash值,定位到Segment的数组的具体位置;
  5. 插入或者覆盖HashEntry对象;
  6. 释放锁。

 ConcurrentHashMap在读写时都需要二次定位,首先定位到segment,之后定位到该segment的具体数组下标。

      由于ConcurrentHashMap中对每一个Segment都各自加锁,保证了多线程下的数据安全,那么在调用Size()的时候,解决一致性也是一个问题,Size方法的目的是统计ConcurrentHashMap的总的元素个数,要将每一个Segment内部的元素的个数都统计得到总数;但是,如在在刚刚统计结束的Segment中瞬间插入一个新的元素,那又怎么解决?

       其Size方法是一个嵌套循环。

  1. 遍历所有的Segment。
  2. 把Segment的元素数量累加起来。
  3. 把Segment的修改次数累加起来。
  4. 判断所有Segment的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程中有修改,重新统计,尝试次数+1;如果不是。说明没有修改,统计结束。
  5. 如果尝试次数超过阈值,则对每一个Segment加锁,再重新统计。
  6. 再次判断所有Segment的总修改次数是否大于上一次的总修改次数。由于已经加锁,次数一定和上次相等。
  7. 释放锁,统计结束。

     为了尽量不锁住所有Segment,首先乐观地假设Size过程中不会有修改(CAS)。当尝试一定次数,才无奈转为悲观锁,锁住所有Segment保证强一致性。

以上是关于ConcurrentHashMap的主要内容,如果未能解决你的问题,请参考以下文章