迭代器中的快速失败(fail-fasr)和安全失败(fail-safe)
首先总体区别下什么是快速失败和安全失败,在java.util包的集合类就是快速失败的,而java.util.concurrent包下的类都是安全失败的。比如ConcurrentHashMap。
快速失败(fail-fast)
在使用迭代器时,如果A线程正在对集合进行遍历,同一时间,B线程对集合进行了增删改的操作,那么就会导致A线程抛出ConcurrentModificationException 异常。
Iterator源码:
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];
}
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();
}
}
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
}
解析:通过查看源码我们发现异常是由checkForComodification()方法抛出的。在变量modCount是当前集合的版本号,每次对集合的增删改都会加1;expectedModCount 是当前迭代器的版本号,在迭代器初始化时,赋值了modCount的值。我们看到了checkForComodification()方法中就是在验证modCount和expectedModCount的值,当你对集合进行了增删改查,那么modCount的值加1,而迭代器中的expectedModCount未同步,因此next()才会报错。但是为什么迭代器的remove不会报错呢?因为有一行为expectedModCount = modCount;同步了两个值,所以才不会抛出异常。
注意:这里的异常条件是modCount != expectedModCount,如果集合发生变化时,modCount恰好和expectedModCount相同,则异常不会抛出。所以我们不能通过捕捉异常这种方式,来做并发处理。
安全失败(fail-safe)
其实就是错误的程序,生成错误的信息,但是信息没有立刻被抛出来,某个时刻突然爆发出来。
如果相对于迭代的快速失败来说,采用安全失败的集合容器,在遍历时,不是直接在集合内容*问,而是先复制原有集合内容,在拷贝的集合上进行遍历。由于迭代时,是操作拷贝的集合,对原集合不影响,就不会抛出异常。
实例代码如下:
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap();
concurrentHashMap.put("不只Java-1", 1);
concurrentHashMap.put("不只Java-2", 2);
concurrentHashMap.put("不只Java-3", 3);
Set set = concurrentHashMap.entrySet();
Iterator iterator = set.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
concurrentHashMap.put("下次循环正常执行", 4);
}
System.out.println("程序结束");
注意:通过上述方式,确实可以避免抛出ConcurrentModificationException异常。但是迭代器就不能访问到修改后的内容了。会造成脏数据的。
解决:
如果是单线程如下:
public class NoFailFastSingleThread {
public static void main(String[] args) {
List<String> lists = new ArrayList<>(10);
for (int i = 0; i < 4; i++){
lists.add(String.valueOf(i));
}
Iterator<String> iterator = lists.iterator();
while (iterator.hasNext()){
String next = iterator.next();
if (next != null){
iterator.remove();
}
}
}
}
如果是多线程如下:
使用java并发包下的类来代替对应的集合,如CopyOnWriteArrayList代替ArrayList。笔记中体现。
上一篇: java 快速失败(fail—fast)和 安全失败(fail—safe)
下一篇: git设置代理