假如有两个线程A与线程B,线程A对集合C进行迭代操作时,线程B改变了集合C的数据结构,此时会报出ConcurrentModificationException异常,这就是Fail-Fast(快速失败),其为java中集合操作的快速检错机制。留下30分钟,一看究竟。
一、Fail-Fast案例
public class FailFastTest {
private static List<Integer> list = new ArrayList<Integer>();
private static class ThreadOne extends Thread{
public void run(){
Iterator<Integer> it = list.iterator();
while(it.hasNext()){
Integer i = it.next();
System.out.println("ThreadOne is using : "+ i);
try {
Thread.sleep(5);
} catch (Exception e) {
e.getStackTrace();
}
}
}
}
private static class ThreadTwo extends Thread{
public void run(){
int i = 0;
while(i < 6){
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进行迭代操作时,ThreadTwo对其进行数据结构的修改,程序报出异常提醒
二、源码解读
// JDK1.8内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; // 由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];
}
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();
}
}
@Override
@SuppressWarnings("unchecked")
public void forEachRemaining(Consumer<? super E> consumer) {
Objects.requireNonNull(consumer);
final int size = ArrayList.this.size;
int i = cursor;
if (i >= size) {
return;
}
final Object[] elementData = ArrayList.this.elementData;
if (i >= elementData.length) {
throw new ConcurrentModificationException();
}
while (i != size && modCount == expectedModCount) {
consumer.accept((E) elementData[i++]);
}
// update once at end of iteration to reduce heap write traffic
cursor = i;
lastRet = i - 1;
checkForComodification();
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
// 在迭代器中的next()、remove()和forEachRemaining()方法中
// 均包含checkForComodification(),而checkForComodification() // 方法
// 会抛出ConcurrentModificationException()异常,
// 关键在于检测modCount与expectedModCount是否相同
//JDK1.8内ArrayList中包含的方法,大都包含modCount++操作
public E remove(int index) {
rangeCheck(index);
modCount++;
E oldValue = elementData(index);
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
return oldValue;
}
private void fastRemove(int index) {
modCount++;
int numMoved = size - index - 1;
if (numMoved > 0)
System.arraycopy(elementData, index+1, elementData, index,
numMoved);
elementData[--size] = null; // clear to let GC do its work
}
- 对ArrayList进行迭代时,会不断检测expectedCount和modCount的值,如果其他操作修改了全局的modCount的值,则会报出异常;
三、解决方案
- 推荐使用CopyOnWriteArrayList,先来解读一下源码:
// JDK1.8内CopyOnWriteArrayList中迭代器不进行expectedCount和modCount检查
static final class COWIterator<E> implements ListIterator<E> {
/** Snapshot of the array */
private final Object[] snapshot;
/** Index of element to be returned by subsequent call to next. */
private int cursor;
private COWIterator(Object[] elements, int initialCursor) {
cursor = initialCursor;
snapshot = elements;
}
public boolean hasNext() {
return cursor < snapshot.length;
}
public boolean hasPrevious() {
return cursor > 0;
}
@SuppressWarnings("unchecked")
public E next() {
if (! hasNext())
throw new NoSuchElementException();
return (E) snapshot[cursor++];
}
// JDK1.8内CopyOnWriteArrayList中迭代器不进行expectedCount和modCount检查
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
// add()方法,本质是对于原数组的copy,不会存在修改集合数据结构的操作,其他方法类似
CopyOnWriteArrayList的核心原理:对于Array的操作(增删改查),都是基于复制到新copy数组,不会改变迭代器的对象,修改完成后改变原有数据的引用即可。
今天就暂时到这里啦!毕业季在匆忙与顾念中离去,回首一年前的毕业季,做好眼前事,珍惜校园时光。