背景知识:我们知道
HashMap
是线程不安全的,多线程情况下,进行put
操作会引起死循环,导致CPU
利用率比较高,所以在并发情况下不能使用HashMap
;而
Hashtable
是线程安全的,其实就是在put
操作的时候加了syncronized
, 但是当一个线程在进行put
操作时,其它的线程连get
操作也不能进行,导致阻塞或者轮询状态,所以竞争越来越激烈,效率低下。
目录结构:
1 | public class ConcurrentHashMap<K,V> extends AbstractMap<K,V> |
锁分段技术
Hashtable
在多并发的情况下效率低下,是因为很多线程争夺同一把锁,而假如容器内有多把锁,每一把锁用于锁容器内的一部分数据,当多线程访问容器内不同段的数据时,线程间就不会存在锁竞争,从而有效的提高并发访问效率。
Segment
英文翻译:” 段“
内部结构是:二级的哈希表,如下图:
多个Segment
保存在一个名为segments
数组当中,每个 Segment
高度自治,各个 Segment
之前读写互不影响。
读写操作
get
想要获取里面的元素,按照结构,我们大概也能猜测出如何获取。
- 首先根据
key
计算的hash
值; - 计算得出 在
segments
数组中的下标,根据下标得出相对应的Segment
; - 再根据
key
的hash
值,找到Segment
数组中的位置
- 首先根据
put
put 元素
- 1.首先根据
key
计算hash
值 - 2.通过
hash
值,定位到具体的Segment
对象 - 3.获取可重入锁
- 4.再次通过
hash
值,定位到Segment
数组中的具体位置 - 5.插入或覆盖原
HashEntry
对象 - 6.释放锁
- 1.首先根据
统计Size大小
jdk 1.7
:
1 | public int size() { |
- 遍历所有的
Segment
- 把
Segment
元素数量累加起来 - 把
Segment
修改次数累加起来 - 判断所有的
Segment
的总修改次数是否大于上一次的总修改次数。如果大于,说明统计过程有修改,尝试统计,统计次数+1;反之,统计结束。 - 如果统计次数超过阀值,则对每个
Segment
加锁,重新统计 - 再次判断总修改次数是否大于上一次的总修改次数,
- 释放锁,统计结束。
- 遍历所有的