JDK1.7-ConcurrentHashMap原理

结构

  • ConcurrentHashMap中有一个Segment数组。每个Segment表示一个分段锁。
  • 每个Segment对象中,有一个HashEntry数组。
  • 每个HashEntry表示一个键值对。HashEntry还有一个next属性,可以形成一个链表。
  • 每个Segment实例都有一个count来表示该分段包含的HashEntry“Key-Value对”总数,即HashEntry的总数。
  • 之所以在每个Segment实例中包含一个计数器,而不是在ConcurrentHashMap中使用全局的计数器,是为了避免出现“全局热点”而影响并发性。
  • 在HashEntry类中,key、hash和next字段都被声明为final型,value字段被声明为volatile型。
  • 在ConcurrentHashMap中,哈希时如果产生“碰撞”,将采用“分离链接法”来处理:把“碰撞”的HashEntry对象链接成一个链表,形成一个桶。由于HashEntry的next字段为final型,因此新节点只能在链表的表头处插入。链表顺序,与插入的顺序相反。

get操作

  • 根据key,计算哈希码
  • 根据哈希码,计算分段锁在数组中的索引。
  • 根据分段锁在数组中的索引,获取分段锁对象,并取到其中的table(HashEntry数组)。通过UNSAFE.getObjectVolatile()方法操作,从主存中读取,确保读到最新的值。
  • 根据哈希码,取到HashEntry对象,也即一个链表,再对链表进行遍历。
  • 遍历过程中,根据key和hash,找到对应元素的value,返回。
  • get为只读操作,不修改内容,所以不加锁。但要保证多线程的可见性。

put操作

说明:

  • 有两个添加方法,put,putIfAbsent。
  • 分段锁的put操作,是要进行加锁的。

操作步骤:

  • 根据key,计算哈希码
  • 根据哈希码,计算分段锁在数组中的索引。
  • 根据分段锁索引,获取分段锁,如果获取到的为空,则构造一个。UNSAFE.getObject()方法。
  • 调用分段锁的put方法添加元素。
  • 下面是分段锁的put方法逻辑。
  • 加锁,成功后继续。
  • 根据哈希码,计算出对象HashEntry对象的下标。
  • 根据下标,取出HashEntry对象,即一个链表。也可能取到空。
  • 如果取到的是一个非空的链表,遍历链表。如果找到对应的HashEntry,根据onlyIfAbsent,决定是否替换。
  • 如果取到的是空,或上一步直至最终也没有找到匹配元素,则在链表头部添加一个节点,表示本次要添加的元素。
  • 将此Segment中的元素总数计数器:count加1
  • 若元素超过阈值,则进行扩容。否则,将HashEntry数组的下标指标新增的HashEntry元素。因为此元素,是链表的头节点了。调用UnSafe的putOrderedObject()方法来更改数组元素引用,这样就保证了其他线程在读取时可以读到最新的值

--结束--

上一篇:ConCurrentHashMap在1.7和1.8区别


下一篇:Java并发46:并发集合系列-基于锁分段技术的ConcurrentHashMap