通告:个人博客迁往 https://www.cnblogs.com/xsl-thumb-rfcs
Java HashMap遍历过程中如果元素被修改会导致遍历失败,ConcurrentHashMap则不会有这个问题。由此引出HashMap的fast-fail机制和ConcurrentHashMap的的fail-safe机制。
看如下实例。
首先看HashMap的fast-fail
package com;
import java.util.HashMap;
import java.util.Map;
public class TestHashMapFastFail
{
public static void main(String[] args)
{
System.out.println("test HashMap fast-fail");
Map testHashMap = new HashMap();
testHashMap.put(1000, "1000");
testHashMap.put(2000, "2000");
testHashMap.put(3000, "3000");
testHashMap.put(4000, "4000");
testHashMap.put(5000, "5000");
System.out.println(testHashMap.size());
for (Map.Entry entry : testHashMap.entrySet())
{
int key = entry.getKey();
System.out.println("key=" + key);
if (key == 3000)
{
testHashMap.remove(key);
}
}
System.out.println(testHashMap.size());
for (Map.Entry entry : testHashMap.entrySet())
{
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
}
运行结果
test HashMap
5
key=2000
key=4000
key=1000
key=3000
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.HashMap$HashIterator.nextNode(HashMap.java:1437)
at java.util.HashMap$EntryIterator.next(HashMap.java:1471)
at java.util.HashMap$EntryIterator.next(HashMap.java:1469)
at com.TestHashMapFastFail.main(TestHashMapFastFail.java:18)
可以看到执行remove操作后,下一轮迭代立刻失效,并抛出异常,这就是所谓的fast-fail。
再看ConcurrentHashMap的fail-safe
package com;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class TestConcurrentHashMapFailSafe
{
public static void main(String[] args)
{
System.out.println("test ConcurrentHashMap fast-fail");
Map testConcurrentHashMap = new ConcurrentHashMap();
testConcurrentHashMap.put(100, "100");
testConcurrentHashMap.put(200, "200");
testConcurrentHashMap.put(300, "300");
testConcurrentHashMap.put(400, "400");
testConcurrentHashMap.put(500, "500");
System.out.println(testConcurrentHashMap.size());
for (Map.Entry entry : testConcurrentHashMap.entrySet())
{
int key = entry.getKey();
System.out.println("key=" + key);
if (key == 300)
{
testConcurrentHashMap.remove(key);
}
}
System.out.println(testConcurrentHashMap.size());
for (Map.Entry entry : testConcurrentHashMap.entrySet())
{
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
}
运行结果
test ConcurrentHashMap fast-fail
5
key=400
key=100
key=500
key=200
key=300
3
100-->100
500-->500
300-->300
可以看出,尽管在迭代过程中执行了remove操作,但是ConcurrentHashMap对外的表现仍然正常,这就是所谓的fail-safe。原因在于ConcurrentHashMap返回的迭代器是弱一致性,ConcurrentHashMap底层数据结构改变时并且不会抛出ConcurrentModificationException异常。
所以,这也是选择ConcurrentHashMap可以获得的一个额外好处,或者说是解决HashMap fast-fail的一种方法,还有一个方法就是使用迭代器的remove方法而不是使用集合的remove方法,示例如下。
package com;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
public class TestHashMapFastFail2
{
public static void main(String[] args)
{
System.out.println("test solve HashMap fast-fail");
Map testHashMap = new HashMap();
testHashMap.put(1000, "1000");
testHashMap.put(2000, "2000");
testHashMap.put(3000, "3000");
testHashMap.put(4000, "4000");
testHashMap.put(5000, "5000");
System.out.println(testHashMap.size());
Iterator iterator = testHashMap.entrySet().iterator();
while (iterator.hasNext())
{
int key = (int)((Map.Entry)iterator.next()).getKey();
System.out.println("key=" + key);
if (key == 2000 || key == 4000)
{
iterator.remove();
}
}
System.out.println(testHashMap.size());
for (Map.Entry entry : testHashMap.entrySet())
{
System.out.println(entry.getKey() + "-->" + entry.getValue());
}
}
}
运行结果
test solve HashMap fast-fail
5
key=2000
key=4000
key=1000
key=3000
key=5000
3
1000-->1000
3000-->3000
5000-->5000
集合的 fast-fail 问题是初学者很容易犯的错误。
说说fast-fail机制和fail-safe机制设计的原因。有人可能会问,既然fast-fail有这么多弊端,为什么还要设计呢,以HashMap为例,因为HashMap本身就是设计成线程不安全的,不支持多个线程同时安全修改,但这也意味着HashMap有较快的速度。fail-safe机制设计的初衷就是保证多线程并发安全地修改集合或Map类。当然,本文的用例都是单线程中的修改操作,主要是为了引出这2个概念。至于内部实现机制,看源码吧。