整体思路
我理解的ConcurrentHashMap1.7整体就三件事(建议复习时候看,如果第一次看跳过这三张图):
第一件事源码流程图:
第二件事:
第三件事:
回忆HashMap多线程问题
在说ConcurrentHashMap之前我们回忆一下我们常用的HashMap在多线程环境中会有什么问题:
当有多个线程同时操作hashmap的时候很可能造成循环链表从而导致在get的时候陷入死循环,而ConcurrentHashMap就是问了解决多线程下hashmap的问题。
ConcurrentHashMap的基本原理
不同与hashmap,ConcurrentHashMap的结果组成如下:
图一
下面我们从具体的代码开始慢慢揭开它的面纱
1. ConcurrentHashMap的构造器:
参数说明
首先我们观察一下传入的参数,前两个不多赘述,我们主要看比hashmap多出来的concurrencyLevel并行度,这个就是用来影响我么图1中所说的segment的数量的,不过这个数量也同initialCapacity参数一样并不是用户传入多少就是多少,而是取2的幂次且大于并且最接近用户传入的数值(例如,出入initialCapacity=15,而实际创建的为16)。
代码主体分析
803-807行主要是做一些参数必要判断。而第808-813就是我们说的对concurrencyLevel这一参数的计算及赋值因为ssize的初始值为1对其就行位运算保证其为2的幂次,直到符合是取2的幂次且大于并且最接近用户传入的数值这一要求(这里之所以要是2的幂次是为了再算我们的元素要存入哪个角标位的用&运算,具体看hashmap篇)
816-820就是为了计算c而c值的计算结果又影响cap的计算结构,所以816-823实际上再算的就是图1中所说的每个segment底下应该放几个hashtable,先不看代码,我们已知initialCapacity和segment的总个数(ssize)那么要计算每个segment底下应该有几个hashtable那就是用initialCapacity/ssize=c(每个segment下的hashtable数),但是我们如果c为小数那么我们应该向下取整还是向上取整呢,很显然我们应该向上取整,及只能让hashtable多,而不应该少,那么816-823实际做的就是这个,818为计算c,819-820为保证向上取整,我们看到821中cap有个最小值为2,822-823保证每个segment下的hashtable为2的幂次。之后就是初始化segment了。
segment的结构
从上可知segment的结构,前2个不说了同hashmap意思一致,第三个就是我们图1的hashtable。
回到ConcurrentHashMap的构造函数:
Segment<K,V> s0 =
new Segment<K,V>(loadFactor, (int)(cap * loadFactor),
(HashEntry<K,V>[])new HashEntry[cap]);
Segment<K,V>[] ss = (Segment<K,V>[])new Segment[ssize];
UNSAFE.putOrderedObject(ss, SBASE, s0); // ordered write of segments[0]
this.segments = ss;
首先是new了一个segment的结构,这个s0其实就是个范本目的就是当后边在创建segment的时候不需要在计算cap等值了,然后创建了整个segment数组当然将范本存入了ss的0位置。
至此整个构造函数就完结了,它主要就是:
- 计算了segment数组大小。
- segment对象的参数(cap及segment底下的hashtable数量)
- 并构造了segment数组
2. ConcurrentHashMap的put方法:
再说之前我们要补充一个unsafe方法:
UNSAFE.getObject (数组名, 偏移地址)
这个函数可以取到主存中的对应数组名称的数组值,主存和工作内存具体看JMM模型
put方法
1 public V put(K key, V value) {
2 Segment<K,V> s;
3 if (value == null)
4 throw new NullPointerException();
5 int hash = hash(key);
6 int j = (hash >>> segmentShift) & segmentMask;
7 if ((s = (Segment<K,V>)UNSAFE.getObject // nonvolatile; recheck
8 (segments, (j << SSHIFT) + SBASE)) == null) // in ensureSegment
9 s = ensureSegment(j);
10 return s.put(key, hash, value, false);
11}
从代码3-4可知ConcurrentHashMap他不能有null的value,5-6行为计算key的hash并获得应该存入的segment下标j,之后的if操作就是判断segments数组对应的下标j中是否初始化了segment对象,我们先假设没有创建即走了s = ensureSegment(j);
ensureSegment
1 private Segment