我是靠谱客的博主 舒适钢笔,这篇文章主要介绍java中fail-fast机制详解,现在分享给大家,希望可以做个参考。

java中fail-fast机制详解

最近遇到一个HashMap相关的问题:如果HashMap在扩容的时候插入一组元素,这组元素能够插入成功吗?于是想到了fail-fast机制,以下是对于fail-fast的一些分析。


以ArrayList的源码为例:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public static void main(String[] args) { List<String> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { list.add(i + ""); } Iterator<String> iterator = list.iterator(); int i = 0; while (iterator.hasNext()) { if (i == 3) { list.remove(3); } System.out.println(iterator.next()); i++; } }

打印的结果为以下,会报出ConcurrentModificationException的异常

复制代码
1
2
3
4
5
6
7
0 1 2 Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:901) at java.util.ArrayList$Itr.next(ArrayList.java:851) at generic.Generic.main(Generic.java:35)

ArrayList中的list.iterator()方法:

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/** * Returns an iterator over the elements in this list in proper sequence. * * <p>The returned iterator is <a href="#fail-fast"><i>fail-fast</i></a>. * * @return an iterator over the elements in this list in proper sequence */ public Iterator<E> iterator() { return new Itr();//此处调用下面的内部类Itr } /** * An optimized version of AbstractList.Itr */ //该内部类实现了Iterator接口,所以使得ArrayList可迭代 private class Itr implements Iterator<E> { int cursor; // index of next element to return int lastRet = -1; // index of last element returned; -1 if no such int expectedModCount = modCount; public boolean hasNext() { return cursor != size; } @SuppressWarnings("unchecked") public E next() { checkForComodification(); int i = cursor; if (i >= size) throw new NoSuchElementException(); Object[] elementData = ArrayList.this.elementData; if (i >= elementData.length) throw new ConcurrentModificationException(); cursor = i + 1; return (E) elementData[lastRet = i]; }

分析Itr类的next()方法可以发现,在最开始的地方调用了checkForComodification()方法,此方法的作用就是来判断是List是否改变,源码如下:

复制代码
1
2
3
4
5
6
//modCount用于记录集合操作过程中作的修改次数 //expectedCount指的是期望的modCount,初始化的时候这两个值是相等的 final void checkForComodification() { if (modCount != expectedModCount) throw new ConcurrentModificationException(); }

如果modCount不等于expectedModCount那么就会抛出ConcurrentModificationException异常,那么什么时候modCount会改变?
当执行add方法的时候,会调用ensureCapacityInternal,该方法会将modCount++,同理,在调用remove,clear方法的时候modCount++。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public boolean add(E e) { ensureCapacityInternal(size + 1); // Increments modCount!! elementData[size++] = e; return true; } private void ensureExplicitCapacity(int minCapacity) { modCount++; // overflow-conscious code if (minCapacity - elementData.length > 0) grow(minCapacity); } /** *下面是Itr类中的remove方法 *每一次remove执行,都会把modCount与expectModCount变为一致 **/ public void remove() { if (lastRet < 0) throw new IllegalStateException(); checkForComodification(); try { ArrayList.this.remove(lastRet); cursor = lastRet; lastRet = -1; expectedModCount = modCount; } catch (IndexOutOfBoundsException ex) { throw new ConcurrentModificationException(); } }

此时需要注意,我们调用的是ArrayList中的add,clear,remove方法,因为ArrayList还有个内部类Itr,其中也有这几个方法,当Itr中方法的时候,modCount与expectModCount一直是一致的,并且不会改变。
所以,因为每一次执行next方法的时候都会先调用方法判断两个Count是否一致,所以当执行list.remove()方法后,modCount+1,而expectModCount并没有改变,所以就会抛出ConcurrentModificationException异常。


如何避免fail-fast呢?

1.使用迭代器提供的remove,add,clear等方法,如上面分析,该remove方法并不会修改modCount的值,并且不会对后面的遍历造成影响。
2.使用java并发包(java.util.concurrent)中的类来代替ArrayList 和hashMap。
比如使用 CopyOnWriterArrayList代替ArrayList,CopyOnWriterArrayList在是使用上跟ArrayList几乎一样,CopyOnWriter是写时复制的容器(COW),在读写时是线程安全的。该容器在对add和remove等操作时,并不是在原数组上进行修改,而是将原数组拷贝一份,在新数组上进行修改,待完成后,才将指向旧数组的引用指向新数组,所以对于CopyOnWriterArrayList在迭代过程并不会发生fail-fast现象。但 CopyOnWrite容器只能保证数据的最终一致性,不能保证数据的实时一致性。
对于HashMap,可以使用ConcurrentHashMap,ConcurrentHashMap采用了锁机制,是线程安全的。在迭代方面,ConcurrentHashMap使用了一种不同的迭代方式。在这种迭代方式中,当iterator被创建后集合再发生改变就不再是抛出ConcurrentModificationException,取而代之的是在改变时new新的数据从而不影响原有的数据 ,iterator完成后再将头指针替换为新的数据 ,这样iterator线程可以使用原来老的数据,而写线程也可以并发的完成改变。即迭代不会发生fail-fast,但不保证获取的是最新的数据。


回到最初的问题,如果HashMap扩容过程中有元素插入,会发生什么?
(源码看来看去,在扩容方法执行中并没有检验modCount是否等于expectModCount,所以,个人猜测,这个插入的元素有可能会被漏掉,也有可能会正常插入)
如果调用HashMap中的迭代器,那么会触发fail-fast机制(hashMap中有KeySpliterator,ValueSpliterator,EntrySpliterator)

复制代码
1
2
3
4
5
6
7
8
9
10
Map<String,String> map= new HashMap<String,String>(); map.put("test", "test"); Iterator iterator = map.keySet().iterator(); while (iterator.hasNext()) { map.put("test1", "test1"); }

参考链接:
http://blog.csdn.net/zymx14/article/details/78394464

最后

以上就是舒适钢笔最近收集整理的关于java中fail-fast机制详解的全部内容,更多相关java中fail-fast机制详解内容请搜索靠谱客的其他文章。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(54)

评论列表共有 0 条评论

立即
投稿
返回
顶部