您的位置:首页 > 编程语言 > Java开发

java.util.ConcurrentModificationException --fail-fast机制

2018-03-25 16:08 495 查看
Fail-fast 快速失败;fail-safe 安全失败
java.util包下面的所有的集合类都是快速失败的,而java.util.concurrent包下面的所有的类都是安全失败的。
快速失败的迭代器会抛出ConcurrentModificationException异常,而安全失败的迭代器永远不会抛出这样的异常.
//经常在迭代集合元素时,会想对集合做修改(add/remove)操作,如下 会抛出异常

for (Iterator<Integer> it = list.iterator(); it.hasNext(); ) {
Integer val = it.next();
if (val == 5) {
list.remove(val);
}
}

Fail-fast 是快速失败机制。Java集合的一种错误检测机制。有可能(无法对是否出现不同步并发修改做出任何硬性保证)检测出线程不安全的机制,有些不安全是不会报错的。只会best effort的形式抛出Concurrent Modification Exception。只是检测到了bug。单线程也可能会抛出此类异常。
(1)单线程环境

集合被创建后,在遍历它的过程中修改了结构。fail-fast机制在遍历一个集合时,当集合结构被修改,会抛出Concurrent Modification Exception。(2)多线程环境当一个线程在遍历这个集合,而另一个线程对这个集合的结构进行了修改。
迭代器的遍历是直接访问内部数据,必须保证遍历过程中内部数据不被修改。迭代器内部的两个标记“expectModcount”和“modcount”,
ArrayList中迭代器的源代码:Iterator调用add()、clear()、remove()方法,都会改变ArrayListy元素个数都会改变modCount值,都要调用checkForComodification()方法->检测 modcount == expectcount,
private class Itr implements Iterator<E> {
int cursor;
int lastRet = -1;
int expectedModCount = ArrayList.this.modCount;  //在Iterator中的定义
//modcount是全局变量,protected transient int modCount = 0;
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();
}
}
如果对正在被迭代的集合进行结构上的改变(即对该集合使用add、remove或clear方法),那么迭代器就不再合法(并且在其后使用该迭代器将会有ConcurrentModificationException异常被抛出).
如果使用迭代器自己的remove方法,那么这个迭代器就仍然是合法的。
这里重点理解在遍历的同时还想删除元素时怎么做呢?(or 添加元素)方法1:直接调用集合collection的remove()方法;(出错,)          当集合使用Iterator进行迭代的时候,实际是new Itr()创建一个内部对象,初始化包含对象个数,可以理解为在独立线程中操作的。Iterator创建之后引用指向原来的集合对象。当原来的对象数量发生变化时,这个内部对象索引表内容其实是不会同步的。所以,当索引指针往后移动的时候就找不到要迭代的对象了。内部对象操作时为了避免这种情况都会通过checkForComodification方法检测是否一致,不一致提前抛出异常ConcurrentModifiedException。方法2:调用Iterator的remove()方法。           如果调用Iterator 的remove() 方法来删除的话,则iterator的内部对象个数和原来集合中对象的个数会保持同步,而直接调用集合的remove方法来删除的话,集合中对象的个数会变化而Iterator 内部对象的个数不会变化。(参考源代码)
当打开 Iterator 迭代集合时,同时又在对集合进行修改。有些集合不允许在迭代时删除或添加元素,但是调用 Iterator 的 remove() 方法是个安全的做法。

下面的源码方便理解:【https://blog.csdn.net/chenssy/article/details/38151189】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();
}
}
protected transient int modCount = 0;  //全局变量

//让迭代器str implements Iterator,调用迭代器的remove()、add()等方法。
【https://blog.csdn.net/QH_JAVA/article/details/50154405】

public Iterator<E> iterator() {
return new Itr(); //它返回一个内部类。我们让这个类实现了iterator接口,如下
}
private class Itr implements Iterator<E> {
// 表示下一个要访问的元素的索引,从next()方法的具体实现就可看出
int cursor; // index of next element to return
// 表示上一个访问的元素的索引 ,-1表示没有
int lastRet = -1; // index of last element returned; -1 if no such
// 表示对ArrayList修改次数的期望值,它的初始值为modCount。
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;
// arraylist集合底层就是一个数组,如果下一个值得检索值大于 集合的值则 报异常
if (i >= elementData.length)
throw new ConcurrentModificationException();
//索引值加1
cursor = i + 1;
// 返回之前的当前值的集合的值
return (E) elementData[lastRet = i];
}
// 删除,使用this 调用类的方法而不是内部类的方法
public void remove() {
// 如果不满足则抛出异常
if (lastRet < 0)
throw new IllegalStateException();
// 这个函数的作用就是检测-----expectedModCount == modCount,如果相等则不会抛出异常否则抛出异常
checkForComodification();
try {
//删除集合中的内容,删除的是之前的不是现在的 ,使用this
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
// 使用iterator的方法进行删除的时候会修改expectedModCount的值为modCount 所以这样就不会出现异常
expectedModCount = modCount;
} catch (IndexOutOfBoundsException ex) {
throw new ConcurrentModificationException(); //因为删除之后及时修改,所以不会抛这个异常
}
}
// 这个方法用来检查两个变量的值是否相等
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
fail-fast的解决方法:
所有涉及改变modcount值的地方都加上同步锁,或者直接使用Collections.synchronizedList,但是增删动作的同步锁可能导致遍历被阻塞。
使用CopyOnWriteArrayList来替换ArrayList,不会抛出,,异常,不会改变原来array只会复制一个,在copy的array操作,修改完成之后改变原有数据的引用即可。所以产生大量对象。在 遍历操作数>> 增删等操作时;在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。(1)需要复制集合,产生大量的无效对象,开销大(2)无法保证读取的数据是目前原始数据结构中的数据。

fail-fast的解决方法:所有涉及改变modcount值的地方都加上同步锁,或者直接使用Collections.synchronizedList,但是增删动作的同步锁可能导致遍历被阻塞。
使用CopyOnWriteArrayList来替换ArrayList,不会抛出,,异常,不会改变原来array只会复制一个,在copy的array操作,修改完成之后改变原有数据的引用即可。所以产生大量对象。在 遍历操作数>> 增删等操作时;在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时。(1)需要复制集合,产生大量的无效对象,开销大(2)无法保证读取的数据是目前原始数据结构中的数据。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: