面试题答案
一键面试fail - fast机制的原理
在Java中,当使用迭代器遍历集合时,迭代器会维护一个内部的modCount变量,该变量记录了集合被修改的次数。每次集合结构发生改变(如添加、删除元素),集合内部的modCount会自增。当迭代器的next()
或remove()
方法被调用时,迭代器会检查自身的expectedModCount
与集合的modCount
是否相等。如果不相等,就抛出ConcurrentModificationException
,这就是fail - fast机制,它用于快速检测到集合在迭代过程中被意外修改。
多线程同时访问并修改集合的情况
如果在多线程程序中,多个线程同时访问并修改一个使用迭代器遍历的集合,由于不同线程对集合的修改会导致modCount的变化,迭代器可能会检测到这种不一致,从而抛出ConcurrentModificationException
异常,导致遍历失败。
代码示例展示这种情况
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class FailFastExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Thread thread1 = new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("Thread 1: " + element);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("D");
System.out.println("Thread 2 added element D");
});
thread1.start();
thread2.start();
}
}
在上述代码中,thread1
尝试遍历列表,thread2
在thread1
遍历过程中向列表添加元素。运行代码时,thread1
很可能在遍历过程中抛出ConcurrentModificationException
。
避免因fail - fast机制导致的意外异常
- 使用线程安全的集合:例如
CopyOnWriteArrayList
,它在修改集合时会创建一个新的底层数组,迭代器遍历的是旧的数组,所以不会抛出ConcurrentModificationException
。
import java.util.Iterator;
import java.util.concurrent.CopyOnWriteArrayList;
public class AvoidFailFastExample {
public static void main(String[] args) {
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
list.add("A");
list.add("B");
list.add("C");
Thread thread1 = new Thread(() -> {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("Thread 1: " + element);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread thread2 = new Thread(() -> {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("D");
System.out.println("Thread 2 added element D");
});
thread1.start();
thread2.start();
}
}
- 使用同步块:在遍历和修改集合时使用
synchronized
关键字同步访问集合,确保同一时间只有一个线程能修改集合。
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class SynchronizedExample {
private static final List<String> list = new ArrayList<>();
static {
list.add("A");
list.add("B");
list.add("C");
}
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
synchronized (list) {
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println("Thread 1: " + element);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
});
Thread thread2 = new Thread(() -> {
synchronized (list) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
list.add("D");
System.out.println("Thread 2 added element D");
}
});
thread1.start();
thread2.start();
}
}