结构
- 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()方法来更改数组元素引用,这样就保证了其他线程在读取时可以读到最新的值
--结束--