Fail-fast

什么是fail-fast

百度百科上是这么写的: fail-fast是Java集合(Collection)中的一种错误机制. 我觉得不太全面.

下面来看一下维基百科上这怎么写的:

In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system’s state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast module is detecting errors, then letting the next-highest level of the system handle them.

从上面这段话就能看出来, fail-fast是在系统设计当中的一种错误检测机制, 一旦检测到可能发生错误, 就立即抛出异常, 程序不再继续运行.

集合中的fail-fast

首先, 来复现这个错误:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
List<String> nameList = new ArrayList<String>() {{
  add("张三");
  add("李四");
  add("小王");
  add("丑八怪");
}};

for (String name : nameList) {
  if (name.equals("丑八怪")) {
    nameList.remove(name);
  }
}

运行上面的代码就会抛出这样的异常:

1
2
3
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)

上面的代码是在for-each中要删除集合中的元素而抛出的异常. 同样的, 增加(add())元素也会抛出这个异常.

异常产生的原因

来看一下源码:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public boolean add(E e) {
  ensureCapacityInternal(size + 1);  // Increments modCount!!
  elementData[size++] = e;
  return true;
}

private void ensureCapacityInternal(int minCapacity) {
  ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

private void ensureExplicitCapacity(int minCapacity) {
  modCount++;

	// ...
}

public boolean remove(Object o) {
  if (o == null) {
    for (int index = 0; index < size; index++)
      if (elementData[index] == null) {
        fastRemove(index);
        return true;
      }
  } else {
    for (int index = 0; index < size; index++)
      if (o.equals(elementData[index])) {
        fastRemove(index);
        return true;
      }
  }
  return false;
}

private void fastRemove(int index) {
  modCount++;
  // ...
}

从这段代码可以发现, 对ArrayListadd(), remove(), clear()方法, 只要涉及到改变集合中的元素的个数的方法都会导致modCount的改变, 但是没有对expectedModCount进行改变.

1
2
3
4
final void checkForComodification() {
  if (expectedModCount != modCount)
    throw new ConcurrentModificationException();
}

expectModCountmodCount不同的时候, 就会抛出一开始出现的异常.

避免产生异常

1. 使用普通for循环进行操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
List<String> nameList = new ArrayList<String>() {{
  add("张三");
  add("李四");
  add("小王");
  add("丑八怪");
}};

for (int i = 0; i < nameList.size(); i++) {
  String name = nameList.get(i);

  if (name.equals("丑八怪")) {
    nameList.remove(name);
  }
}

这样虽然不会报错, 但是可能会产生漏删的情况出现.

2. 使用Iterator进行操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
List<String> nameList = new ArrayList<String>() {{
  add("张三");
  add("李四");
  add("小王");
  add("丑八怪");
}};

Iterator<String> iterator = nameList.iterator();

while (iterator.hasNext()) {
  if (iterator.next().equals("丑八怪")) {
    iterator.remove();
  }
}

3. 其实使用for-each也可以

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
List<String> nameList = new ArrayList<String>() {{
  add("张三");
  add("李四");
  add("小王");
  add("丑八怪");
}};

for (String name : nameList) {
  if (name.equals("丑八怪")) {
    nameList.remove(name);
    break;
  }
}

当集合中只删除一个元素的时候, 在删除操作之后立即break, 使循环不进入下一次遍历就可以了.

4. 使用fail-safe的集合类

5. 使用stream中的filter

0%