比方,ArrayList:
注重,迭代器的疾速失利行动没法获得保证,因为一般来说,不能够对是不是涌现差别步并发修正做出任何硬性保证。疾速失利迭代器会尽 最大勤奋抛出 ConcurrentModificationException。因而,为进步这类迭代器的准确性而编写一个依靠于此非常的顺序是毛病的做法:迭 代器的疾速失利行动应当仅用于检测 bug。
HashMap中:
注重,迭代器的疾速失利行动不能获得保证,一般来说,存在非同步的并发修正时,不能够作出任何坚定的保证。疾速失利迭代器尽最大 勤奋抛出 ConcurrentModificationException。因而,编写依靠于此非常的顺序的做法是毛病的,准确做法是:迭代器的疾速失利行动应 该仅用于检测顺序毛病。
在这两段话中重复地提到”疾速失利”。那末作甚”疾速失利”机制呢?
“疾速失利”也就是fail-fast,它是Java鸠合的一种毛病检测机制。当多个线程对鸠合举行构造上的转变的操纵时,有能够会发作fail-fast机制。记着是有能够,而不是肯定。比方:假定存在两个线程(线程1、线程2),线程1经过历程Iterator在遍历鸠合A中的元素,在某个时刻线程2修正了鸠合A的构造(是构造上面的修正,而不是简朴的修正鸠合元素的内容),那末这个时刻顺序就会抛出 ConcurrentModificationException 非常,从而发作fail-fast机制。
一、fail-fast示例
public class FailFastTest { private static List<Integer> list = new ArrayList<>(); /** * @desc:线程one迭代list * @Project:test * @file:FailFastTest.java * @Authro:chenssy * @data:2014年7月26日 */ private static class threadOne extends Thread{ public void run() { Iterator<Integer> iterator = list.iterator(); while(iterator.hasNext()){ int i = iterator.next(); System.out.println("ThreadOne 遍历:" + i); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } } } /** * @desc:当i == 3时,修正list * @Project:test * @file:FailFastTest.java * @Authro:chenssy * @data:2014年7月26日 */ private static class threadTwo extends Thread{ public void run(){ int i = 0 ; while(i < 6){ System.out.println("ThreadTwo run:" + i); if(i == 3){ list.remove(i); } i++; } } } public static void main(String[] args) { for(int i = 0 ; i < 10;i++){ list.add(i); } new threadOne().start(); new threadTwo().start(); } }
运转效果:
ThreadOne 遍历:0 ThreadTwo run:0 ThreadTwo run:1 ThreadTwo run:2 ThreadTwo run:3 ThreadTwo run:4 ThreadTwo run:5 Exception in thread "Thread-0" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at test.ArrayListTest$threadOne.run(ArrayListTest.java:23
二、fail-fast发作缘由
经过历程上面的示例和解说,我开端晓得fail-fast发作的缘由就在于顺序在对 collection 举行迭代时,某个线程对该 collection 在构造上对其做了修正,这时候迭代器就会抛出 ConcurrentModificationException 非常信息,从而发作 fail-fast。
要相识fail-fast机制,我们起首要对ConcurrentModificationException 非常有所相识。当要领检测到对象的并发修正,但不许可这类修正时就抛出该非常。同时须要注重的是,该非常不会一直指出对象已过差别线程并发修正,假如单线程违反了划定规矩,一样也有能够会抛出改非常。
固然,迭代器的疾速失利行动没法获得保证,它不能保证肯定会涌现该毛病,然则疾速失利操纵会尽最大勤奋抛出ConcurrentModificationException非常,所以因而,为进步此类操纵的准确性而编写一个依靠于此非常的顺序是毛病的做法,准确做法是:ConcurrentModificationException 应当仅用于检测 bug。下面我将以ArrayList为例进一步剖析fail-fast发作的缘由。
从前面我们晓得fail-fast是在操纵迭代器时发作的。如今我们来看看ArrayList中迭代器的源代码:
private class Itr implements Iterator<E> { int cursor; int lastRet = -1; int expectedModCount = ArrayList.this.modCount; public boolean hasNext() { return (this.cursor != ArrayList.this.size); } public E next() { checkForComodification(); /** 省略此处代码 */ } public void remove() { if (this.lastRet < 0) throw new IllegalStateException(); checkForComodification(); /** 省略此处代码 */ } final void checkForComodification() { if (ArrayList.this.modCount == this.expectedModCount) return; throw new ConcurrentModificationException(); } }
从上面的源代码我们能够看出,迭代器在挪用next()、remove()要领时都是挪用checkForComodification()要领,该要领重要就是检测modCount == expectedModCount ? 若不等则抛出ConcurrentModificationException 非常,从而发作fail-fast机制。所以要弄清楚为何会发作fail-fast机制我们就必须要用弄邃晓为何modCount != expectedModCount ,他们的值在什么时刻发作转变的。
expectedModCount 是在Itr中定义的:int expectedModCount = ArrayList.this.modCount;所以他的值是不能够会修正的,所以会变的就是modCount。modCount是在 AbstractList 中定义的,为全局变量:
protected transient int modCount = 0;
那末他什么时刻因为何缘由而发作转变呢?请看ArrayList的源码:
public boolean add(E paramE) { ensureCapacityInternal(this.size + 1); /** 省略此处代码 */ } private void ensureCapacityInternal(int paramInt) { if (this.elementData == EMPTY_ELEMENTDATA) paramInt = Math.max(10, paramInt); ensureExplicitCapacity(paramInt); } private void ensureExplicitCapacity(int paramInt) { this.modCount += 1; //修正modCount /** 省略此处代码 */ } public boolean remove(Object paramObject) { int i; if (paramObject == null) for (i = 0; i < this.size; ++i) { if (this.elementData[i] != null) continue; fastRemove(i); return true; } else for (i = 0; i < this.size; ++i) { if (!(paramObject.equals(this.elementData[i]))) continue; fastRemove(i); return true; } return false; } private void fastRemove(int paramInt) { this.modCount += 1; //修正modCount /** 省略此处代码 */ } public void clear() { this.modCount += 1; //修正modCount /** 省略此处代码 */ }
从上面的源代码我们能够看出,ArrayList中不管add、remove、clear要领只如果触及了转变ArrayList元素的个数的要领都邑致使modCount的转变。所以我们这里能够开端判断因为expectedModCount 得值与modCount的转变差别步,致使二者之间不等从而发作fail-fast机制。晓得发作fail-fast发作的根本缘由了,我们能够有以下场景:
有两个线程(线程A,线程B),个中线程A担任遍历list、线程B修正list。线程A在遍历list历程的某个时刻(此时expectedModCount = modCount=N),线程启动,同时线程B增添一个元素,这是modCount的值发作转变(modCount + 1 = N + 1)。线程A继承遍历实行next要领时,公告checkForComodification要领发明expectedModCount = N ,而modCount = N + 1,二者不等,这时候就抛出ConcurrentModificationException 非常,从而发作fail-fast机制。
所以,直到这里我们已完整相识了fail-fast发作的根本缘由了。晓得了缘由就好找处理办法了。
三、fail-fast处理办法
经过历程前面的实例、源码剖析,我想列位已基础相识了fail-fast的机制,下面我就发作的缘由提出处理计划。这里有两种处理计划:
计划一: 在遍历历程当中一切触及到转变modCount值得处所悉数加上synchronized或许直接运用Collections.synchronizedList,如许就能够处理。然则不引荐,因为增删形成的同步锁能够会壅塞遍历操纵。
计划二: 运用CopyOnWriteArrayList来替代ArrayList。引荐运用该计划。
以上就是fail-fast机制的细致内容,更多请关注ki4网别的相干文章!