欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

迭代器中的快速失败(fail-fasr)和安全失败(fail-safe)

程序员文章站 2022-07-14 11:25:00
...

首先总体区别下什么是快速失败和安全失败,在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。笔记中体现。