Java并发编程之ConcurrentHashMap(二)

2014-11-24 09:12:25 · 作者: · 浏览: 1
return segmentFor(hash).get(key, hash);
4
}
看第三行,segmentFor这个函数用于确定操作应该在哪一个segment中进行,几乎对ConcurrentHashMap的所有操作都需要用到这个函数,我们看下这个函数的实现:

1
final Segment segmentFor(int hash) {
2
return segments[(hash >>> segmentShift) & segmentMask];
3
}
这个函数用了位操作来确定Segment,根据传入的hash值向右无符号右移segmentShift位,然后和segmentMask进行与操作,结合我们之前说的segmentShift和segmentMask的值,就可以得出以下结论:假设Segment的数量是2的n次方,根据元素的hash值的高n位就可以确定元素到底在哪一个Segment中。

在确定了需要在哪一个segment中进行操作以后,接下来的事情就是调用对应的Segment的get方法:

01
V get(Object key, int hash) {
02
if (count != 0) { // read-volatile
03
HashEntry e = getFirst(hash);
04
while (e != null) {
05
if (e.hash == hash && key.equals(e.key)) {
06
V v = e.value;
07
if (v != null)
08
return v;
09
return readValueUnderLock(e); // recheck
10
}
11
e = e.next;
12
}
13
}
14
return null;
15
}
先看第二行代码,这里对count进行了一次判断,其中count表示Segment中元素的数量,我们可以来看一下count的定义:

1
transient volatile int count;
可以看到count是volatile的,实际上这里里面利用了volatile的语义:

对volatile字段的写入操作happens-before于每一个后续的同一个字段的读操作。
因为实际上put、remove等操作也会更新count的值,所以当竞争发生的时候,volatile的语义可以保证写操作在读操作之前,也就保证了写操作对后续的读操作都是可见的,这样后面get的后续操作就可以拿到完整的元素内容。

然后,在第三行,调用了getFirst()来取得链表的头部:

1
HashEntry getFirst(int hash) {
2
HashEntry[] tab = table;
3
return tab[hash & (tab.length - 1)];
4
}
同样,这里也是用位操作来确定链表的头部,hash值和HashTable的长度减一做与操作,最后的结果就是hash值的低n位,其中n是HashTable的长度以2为底的结果。

在确定了链表的头部以后,就可以对整个链表进行遍历,看第4行,取出key对应的value的值,如果拿出的value的值是null,则可能这个key,value对正在put的过程中,如果出现这种情况,那么就加锁来保证取出的value是完整的,如果不是null,则直接返回value。

ConcurrentHashMap的put操作
看完了get操作,再看下put操作,put操作的前面也是确定Segment的过程,这里不再赘述,直接看关键的segment的put方法:

01
V put(K key, int hash, V value, boolean onlyIfAbsent) {
02
lock();
03
try {
04
int c = count;
05
if (c++ > threshold) // ensure capacity
06
rehash();
07
HashEntry[] tab = table;
08
int index = hash & (tab.length - 1);
09
HashEntry first = tab[index];
10
HashEntry e = first;
11
while (e != null && (e.hash != hash || !key.equals(e.key)))
12
e = e.next;
13

14
V oldValue;
15
if (e != null) {
16
oldValue = e.value;
17
if (!onlyIfAbsent)
18
e.value = value;
19
}
20
else {
21
oldValue = null;
22
++modCount;
23
tab[index] = new HashEntry(key, hash, first, value);
24
count = c; // write-volatile
25
}
26
return oldValue;
27
} finally {
28
unlock();
29
}
30
}
首先对Segment的put操作是加锁完成的,然后在第五行,如果Segment中元素的数量超过了阈值(由构造函数中的loadFactor算出)这需要进行对Segment扩容,并且要进行rehash,关于rehash的过程大家可以自己去了解,这里不详细讲了。

第8和第9行的操作就是getFirst的过程,确定链表头部的位置。

第11行这里的这个while循环是在链表中寻找和要put的元素相同key的元素,如果找到,就直接更新更新key的value,如果没有找到,则进入21行这里,生成一个新的HashEntry并且把它加到整个Segment的头部,然后再更新count的值。

ConcurrentHashMap的remove操作
Remove操作的前面一部分和前面的get和put操作一样,都是定位Segment的过程,然后再调用Segment的remove方法:

01
V remove(Object key, int hash, Object value) {
02
lock();
03
try {
04
int c = count - 1;
05
HashEntry[] tab = table;
06
int index = hash & (tab.length - 1);
07
HashEntry first = tab[index];
08
HashEntry e = first;
09
while (e != null && (e.hash != hash || !key.equals(e.key)))
10
e = e.next;
11

12
V oldValue = n