解决Java并发修改异常「ConcurrentModificationException」的方法

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中常见的异常之一,在多线程环境下尤其需要注意。为了避免这个异常,可以使用线程安全的集合类、迭代器遍历集合、对集合进行同步、使用并发包中的类等方法。不同的解决方法适用于不同的情况,需要根据具体的情况进行选择。

后端开发标签