前言
最近在研究集合,看了很多篇关于Hashtable文章,多次介绍了Hashtable迭代是fail-safe机制。本人习惯看了文章后自己研究,结果发现迭代的时候remove掉会报异常。按道理来说fail-safe是不会报异常的啊。然后我自己去看Hashtable源码,才发现文章是错误的🙂。
fail-safe和fail-fast
- fail-safe
这种遍历基于容器的一个克隆。因此,对容器内容的修改不影响遍历。java.util.concurrent包下的容器都是安全失败的,可以在多线程下并发使用,并发修改。常见的的使用fail-safe方式遍历的容器有ConcerrentHashMap和CopyOnWriteArrayList等。 - fail-fast
fail-fast的字面意思是“快速失败”。当我们在遍历集合元素的时候,经常会使用迭代器,但在迭代器遍历元素的过程中,如果集合的结构被改变的话,就会抛出异常,防止继续遍历。这就是所谓的快速失败机制。
实验代码
Hashtable<String,String> hash = new Hashtable<>();
hash.put("a","a");
hash.put("b","b");
hash.put("c","c");
for(Map.Entry<String,String> entry : hash.entrySet()){
hash.remove("b");
}
抛出异常:
Exception in thread "main" java.util.ConcurrentModificationException
at java.util.Hashtable$Enumerator.next(Hashtable.java:1387)
at com.example.demo.DemoTest.main(DemoTest.java:21)
以上代码是使用了for直接循环出现异常(原理也是用了迭代器),稍后分析
Hashtable<String, String> hash = new Hashtable<>();
hash.put("a", "a");
hash.put("b", "b");
hash.put("c", "c");
Enumeration<String> enumeration = hash.keys();
while (enumeration.hasMoreElements()) {
hash.remove("a");
String key = enumeration.nextElement();
System.out.println(key);
}
输出:
b
c
使用Enumeration迭代器,没抛异常
以上代码是使用了Enumeration迭代器
,没抛异常,稍后分析。我们接下来分析Hashtable源码
Hashtable源码分析
modCount
/**
* The number of times this Hashtable has been structurally modified
* Structural modifications are those that change the number of entries in
* the Hashtable or otherwise modify its internal structure (e.g.,
* rehash). This field is used to make iterators on Collection-views of
* the Hashtable fail-fast. (See ConcurrentModificationException).
* 由注释可以看出,Hashtable判断fail-fast是根据modCount判断的。
* 如果迭代的时候发现modCount数量不对,就知道在迭代过程中做了删除
* 等处理,则立即抛出异常。
* 记录当前集合被修改的次数
*/
private transient int modCount = 0;
迭代器抛出异常的HashTable1387行
public T next() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
return nextElement();
}
modCount
是刚刚分析的用来记录当前集合被修改的次数,expectedModCount
是进行迭代的时候保存之前修改的次数(protected int expectedModCount = modCount)。当你进行remove操作的时候,modCount
次数会增加,导致expectedModCount
和modCount
不相等,直接抛出异常。这就是fail-fast机制。
Enumeration迭代器
为什么使用Enumeration迭代器
不会抛出异常呢?我们看下Hashtable1362行
public T nextElement() {
Entry<?,?> et = entry;
int i = index;
Entry<?,?>[] t = table;
/* Use locals for faster loop iteration */
while (et == null && i > 0) {
et = t[--i];
}
entry = et;
index = i;
if (et != null) {
Entry<?,?> e = lastReturned = entry;
entry = e.next;
return type == KEYS ? (T)e.key : (type == VALUES ? (T)e.value : (T)e);
}
throw new NoSuchElementException("Hashtable Enumerator");
}
看得出,使用Enumeration迭代器
并没有判断expectedModCount
和modCount
是否相等,所以没有出现ConcurrentModificationException错误。不过注意:虽然是没抛异常,但是迭代时候和Hashtable的table数组共享数据,所以在多线程环境下会有问题。
总结
其实仔细看下·Hashtable·注释,开头就告诉我们
Hashtable
的迭代器其实就是fail-fast
的。网络上很多文章都说Hashtable
是fail-safe
的,是错误的,人云亦云,三人成虎,导致大家都以为Hashtable
是fail-safe
的。学习是要带着实践的精神,不能只看不做,实践才能让你真正懂得原理和文章的正确性。
Hashtable
有两种迭代器:1.Iterator
是fail-fast
机制,也是我们经常用到的;2.Enumeration
其实也不算fail-safe
,因为使用的数据没有copy一份。Enumeration
不会因为你做了删除等处理就会抛异常,但是迭代时候和Hashtable的table数组共享数据,所以在多线程环境下会有问题。
评论区