1. 什么是ConcurrentModificationException
ConcurrentModificationException是Java中的一个异常,它在遍历一个集合时,如果发现在遍历时对集合进行了修改,就会抛出该异常。例如:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
for (String s : list) {
list.remove("a"); // 抛出ConcurrentModificationException
}
这个异常会影响到多线程环境下的程序,因此需要重视。但是,解决这个异常并不是一件很容易的事情,需要根据具体的情形考虑不同的解决方法。
2. ConcurrentModificationException的原因
2.1 集合的fail-fast机制
Java集合提供了fail-fast机制,即在集合被修改时会立即抛出ConcurrentModificationException异常。这是为了保证多线程环境的程序安全。例如:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
Iterator<String> iterator = list.iterator();
list.remove(0); // 抛出ConcurrentModificationException
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
在遍历过程中,如果对集合进行了修改,就会抛出ConcurrentModificationException异常。
2.2 多线程共享集合
当多个线程同时对一个集合进行操作时,如果没有对集合进行同步,就有可能出现ConcurrentModificationException异常。例如:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
new Thread(() -> {
for (String s : list) {
System.out.println(s);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (String s : list) {
System.out.println(s.toUpperCase());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(3000);
list.remove(0); // 抛出ConcurrentModificationException
由于两个线程都在遍历集合,因此在其中一个线程遍历时,另一个线程对集合进行了修改,就会抛出ConcurrentModificationException异常。
3. 解决ConcurrentModificationException的方法
3.1 使用线程安全的集合类
Java提供了许多线程安全的集合类,例如:ConcurrentHashMap、CopyOnWriteArrayList等,它们支持并发修改并且保证线程安全。例如:
List<String> list = new CopyOnWriteArrayList<>();
list.add("a");
list.add("b");
new Thread(() -> {
for (String s : list) {
System.out.println(s);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(() -> {
for (String s : list) {
System.out.println(s.toUpperCase());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(3000);
list.remove(0); // 不会抛出ConcurrentModificationException
在使用线程安全的集合类时,需要注意以下几点:
虽然这些集合类是线程安全的,但是它们不一定是高效的,因此需要根据具体情况选择合适的集合类。
这些集合类虽然线程安全,但是它们仍然可以出现并发修改异常,因为有可能存在多个线程同时对集合进行修改的情况。
这些集合类通常是通过复制来实现并发修改的,因此对它们的修改操作并不会立即反映到原始集合上,可能会导致多个线程看到不同的数据。
3.2 使用迭代器遍历集合
使用迭代器遍历集合可以避免出现ConcurrentModificationException异常,因为在遍历过程中,如果进行了修改操作,就会抛出异常。
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if ("a".equals(s)) {
iterator.remove();
}
}
3.3 对集合进行同步
对集合进行同步也可以避免出现ConcurrentModificationException异常。例如:
List<String> list = new ArrayList<>();
list.add("a");
list.add("b");
synchronized (list) {
for (String s : list) {
System.out.println(s);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
synchronized (list) {
list.remove(0);
}
对集合进行同步需要注意以下几点:
对集合的每个访问都需要进行同步。
如果在同步代码块中进行了复杂的操作,可能会导致性能下降。
同步代码块的范围过大,可能会影响程序的并发性能。
3.4 使用并发包中的类
Java提供了许多并发包中的类,例如:BlockingQueue、ConcurrentLinkedQueue、ConcurrentMap等,它们可以有效地避免出现ConcurrentModificationException异常。
BlockingQueue<String> queue = new LinkedBlockingQueue<>();
queue.put("a");
queue.put("b");
new Thread(() -> {
while (true) {
try {
String s = queue.take();
System.out.println(s);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
Thread.sleep(3000);
queue.remove("a"); // 不会抛出ConcurrentModificationException
使用并发包中的类需要注意以下几点:
这些类通常具有较好的并发性能。
这些类虽然避免了ConcurrentModificationException异常,但是仍然需要注意线程安全问题。
4. 总结
ConcurrentModificationException是Java中常见的异常之一,在多线程环境下尤其需要注意。为了避免这个异常,可以使用线程安全的集合类、迭代器遍历集合、对集合进行同步、使用并发包中的类等方法。不同的解决方法适用于不同的情况,需要根据具体的情况进行选择。