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

首先总体区别下什么是快速失败和安全失败,在java.util包的集合类就是快速失败的,而java.util.concurrent包下的类都是安全失败的。比如ConcurrentHashMap。

快速失败(fail-fast)

在使用迭代器时,如果A线程正在对集合进行遍历,同一时间,B线程对集合进行了增删改的操作,那么就会导致A线程抛出ConcurrentModificationException 异常。

Iterator源码:

private class Itr implements Iterator {
        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 lists = new ArrayList<>(10);
          for (int i = 0; i < 4; i++){
              lists.add(String.valueOf(i));
          }

          Iterator iterator = lists.iterator();
          while (iterator.hasNext()){
              String next = iterator.next();
              if (next != null){
                  iterator.remove();
              }
          }

      }
  }

如果是多线程如下:

使用java并发包下的类来代替对应的集合,如CopyOnWriteArrayList代替ArrayList。笔记中体现。

你可能感兴趣的:(Java面试题库)