侧边栏壁纸
博主头像
Terry

『LESSON 5』

  • 累计撰写 90 篇文章
  • 累计创建 21 个标签
  • 累计收到 1 条评论

目 录CONTENT

文章目录

Hashtable迭代器到底是fail-safe还是fail-fast机制?

Terry
2020-10-11 / 0 评论 / 0 点赞 / 529 阅读 / 3,424 字 / 正在检测是否收录...

前言

最近在研究集合,看了很多篇关于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次数会增加,导致expectedModCountmodCount不相等,直接抛出异常。这就是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迭代器并没有判断expectedModCountmodCount是否相等,所以没有出现ConcurrentModificationException错误。不过注意:虽然是没抛异常,但是迭代时候和Hashtable的table数组共享数据,所以在多线程环境下会有问题。

总结

其实仔细看下·Hashtable·注释,开头就告诉我们Hashtable的迭代器其实就是fail-fast的。网络上很多文章都说Hashtablefail-safe的,是错误的,人云亦云,三人成虎,导致大家都以为Hashtablefail-safe的。学习是要带着实践的精神,不能只看不做,实践才能让你真正懂得原理和文章的正确性。
Hashtable有两种迭代器:1.Iteratorfail-fast机制,也是我们经常用到的;2.Enumeration其实也不算fail-safe,因为使用的数据没有copy一份。Enumeration不会因为你做了删除等处理就会抛异常,但是迭代时候和Hashtable的table数组共享数据,所以在多线程环境下会有问题。

0

评论区