我们知道ConcurrentHashMap和HashTable都是线程安全的HashMap,其中ConcurrentHashMap在性能方面也是完全优于HashTable,那么ConcurrentHashMap可以完全替代HashTable吗?(对于JDK1.6、1.7)
答案是不能,HashTable的迭代器是强一致性的,而ConcurrentHashMap是弱一致性的。ConcurrentHashMap的get、clear、iterator都是弱一致性的。
什么是弱一致性和强一致性?
get方法是弱一致性的,就是你期望往ConcurrentHashMap中加入一个元素后,立马能对get可见,但ConcurrentHashMap并不能如你所愿。put操作将一个元素加入到ConcurrentHashMap后,get可能在某段时间内还不能看到这个元素。
V put(K key, int hash, V value, boolean onlyIfAbsent) {
lock();
try {
int c = count;
if (c++ > threshold) // ensure capacity
rehash();
HashEntry[] tab = table;
int index = hash & (tab.length - 1);
HashEntry first = tab[index];
HashEntry e = first;
while (e != null && (e.hash != hash || !key.equals(e.key)))
e = e.next;
V oldValue;
if (e != null) {
oldValue = e.value;
if (!onlyIfAbsent)
e.value = value;
}
else {
oldValue = null;
++modCount;
tab[index] = new HashEntry(key, hash, first, value); // 1
count = c; // write-volatile 2
}
return oldValue;
} finally {
unlock();
}
}
V get(Object key, int hash) {
if (count != 0) { // read-volatile 3
HashEntry e = getFirst(hash); //4
while (e != null) {
if (e.hash == hash && key.equals(e.key)) {
V v = e.value;
if (v != null)
return v;
return readValueUnderLock(e); // recheck
}
e = e.next;
}
}
return null;
}
我们可以核心关注上面代码块中的【1,2】和【3,4】语句,其中【1,2】为put元素,【3,4】为get元素,其中put时加了锁,而get没有加锁,当发生下面这种运行顺序的时候,就可能导致get不能立即看到put所进行的操作。
执行put的线程 | 执行get的线程 |
⑧tab[index] = new HashEntry<K,V>(key, hash, first, value) | |
③if (count != 0) | |
②count = c | |
⑨HashEntry e = getFirst(hash); |
我们通过分析,发现可能存在已将数据put进数组,但put中count还未写入,get中count已被读取,那么可能就会导致get中读取不到刚才已经被put进的数据。还有getFirst(hash)先执行,tab[index] = new HashEntry<K,V>(key, hash, first, value)后执行,那么这个get操作也是看不到put的操作的。
发生这种现象的根本原因是get操作几乎是一个无锁的操作,使得同一个Segment上的get和put可以同时进行,这是get是弱一致性的根本原因。
public void clear() {
for (int i = 0; i < segments.length; ++i)
segments[i].clear();
}
clear方法中,因为没有全局的锁,就可能会导致在清理完一个Segment后,清理下一个Segment的时候,已经被清理的Segment可能又会被加入数据,这就会导致clear返回的时候,ConcurrentHashMap中存在数据。这就是clear方法弱一致性的原因。
ConcurrentHashMap的弱一致性主要是为了提高效率,是一致性和效率上的一种均衡,如果要实现强一致性,就可能到处加锁甚至全局锁,那就和HashTable基本一样了。