fail-fast
- 认识fail-fast (快速失败)
当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。 - 写个例子:
public static void main(String[] args) {
ArrayList test1 = new ArrayList();
for(int i =0;i<10;i++){
test1.add(“a" + i);
}
Thread testa = new Thread(){
@Override
public void run() {
for (int i =0 ;i<7 ;i++){
System.out.println("testa---- 运行a");
test1.add(“1a"+i);
}
}
};
Thread testb =new Thread(){
@Override
public void run() {
for (int i =0 ;i<7 ;i++){
System.out.println("testb---- 运行b");
test1.add(“2b"+i);
}
}
};
Thread testc =new Thread(){
@Override
public void run() {
for (int i =0 ;i<7 ;i++){
System.out.println("testc---- 运行c");
test1.iterator().next();
}
}
};
testa.start();
testb.start();
testc.start();
}
上述例子中:使用多个线程去加载执行ArrayList,添加操作、next操作都并行处理,就会出现ConcurrentModificationException异常。
出现异常的原因则是因为:next操作中进行了一个检查操作;
ublic Iterator<E> iterator() {
return new Itr();
}
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];
}
………………
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
在checkForComodification方法中进行了一个检查,modCount和expectedModCount的比较;
- modCount :
/**
* The number of times this list has been <i>structurally modified</i>.
* Structural modifications are those that change the size of the
* list, or otherwise perturb it in such a fashion that iterations in
* progress may yield incorrect results.
*
* <p>This field is used by the iterator and list iterator implementation
* returned by the {@code iterator} and {@code listIterator} methods.
* If the value of this field changes unexpectedly, the iterator (or list
* iterator) will throw a {@code ConcurrentModificationException} in
* response to the {@code next}, {@code remove}, {@code previous},
* {@code set} or {@code add} operations. This provides
* <i>fail-fast</i> behavior, rather than non-deterministic behavior in
* the face of concurrent modification during iteration.
*
* <p><b>Use of this field by subclasses is optional.</b> If a subclass
* wishes to provide fail-fast iterators (and list iterators), then it
* merely has to increment this field in its {@code add(int, E)} and
* {@code remove(int)} methods (and any other methods that it overrides
* that result in structural modifications to the list). A single call to
* {@code add(int, E)} or {@code remove(int)} must add no more than
* one to this field, or the iterators (and list iterators) will throw
* bogus {@code ConcurrentModificationExceptions}. If an implementation
* does not wish to provide fail-fast iterators, this field may be
* ignored.
*/
protected transient int modCount = 0;
从注释中可以看出modCount是表示:此list被修改的次数;在next、remove、previous方法中会出现ConcurrentModificationException异常。
在ArrayList中许多方法都会去操作modCount,例如:add、remove、clear等等。
我们可以想象在多线程操作中:
a线程完成了删除某个元素的操作,b线程正在遍历、c线程也准备删除某个元素;又恰好c线程要删除的元素就是a线程已经删除的元素,那么c线程将无元素可删除;同时b线程在a线程删除前已经遍历过该元素了,但是实际上元素List并没有该元素,这是不正确的。这样就会造成数据的混乱。
这就是一个简单的fail-fast事件。
有快速失败(fail-fast),就会有安全失败(fail-safe)。
fail-safe
是指:在对数据操作的情况下,对于数据操作进行拷贝操作,你可以简单的理解为将数据复制一份,对数据的操作是在拷贝数据的基础上操作的,但是对原数据修改你是不知道的,因此在安全失败机制中,你可以并发读取,不会抛出异常,但是不能保证当前读取的数据和对象集合的数据是一致的。
从另一个角度来理解的话:
有a、b两个线程同时读取对象集合list,a线程遍历List是获取了一份当前快照,然后b线程对数据进行修改也是获取了当前快照;然后b线程变更了数据的内容,a线程继续遍历List,此时a线程是不会知道b线程的修改操作,因此两个线程操作的是各自的快照版本,是内存中不同的两个地址。
java集合中:
fail-fast | fail-fast |
---|---|
ArrayList、HashMap、HashSet、Vector | CopyOnWriteArrayList、ConcurrentHashMap |
参考资料:
面试笔记–Fast-Fail(快速失败)机制