如何解决Java并发修改异常「ConcurrentModificationException」

1. 了解ConcurrentModificationException

在Java编程中,ConcurrentModificationException是一个较为常见的异常,它主要是在多线程并发操作集合类的时候可能会出现。例如:在一个线程遍历了一个List集合中的元素,并且另一个线程修改了List中的元素时,就会抛出此异常。下面看一下代码示例:

public static void main(String[] args) {

List<String> list = new ArrayList<>();

list.add("A");

list.add("B");

list.add("C");

for (String str : list) {

System.out.println(str);

list.remove(str);

}

}

运行上述代码时,会出现如下异常提示:

Exception in thread "main" java.util.ConcurrentModificationException

那么如何解决这种并发修改异常呢?

2. 使用Iterator进行遍历

在上面的示例代码中,我们使用的是for循环来遍历List集合中的元素,这种方式虽然非常方便,但是它是不安全的。我们可以使用Iterator来代替for循环,这样可以避免出现ConcurrentModificationException异常。

2.1 操作方法

下面是使用Iterator进行遍历的代码示例:

List<String> list = new ArrayList<>();

list.add("A");

list.add("B");

list.add("C");

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

while (iterator.hasNext()) {

String str = iterator.next();

System.out.println(str);

iterator.remove();

}

运行上述代码,输出结果是:

A

B

C

正如你所看到的,Iterator遍历List集合时,并不是像for-each循环那样直接操作集合中的对象,而是通过iterator对象去操作List集合。

2.2 代码演示

下面是通过一个例子演示在并发情况下使用Iterator进行遍历的代码:

public static void main(String[] args) {

List<String> list = new ArrayList<>();

list.add("A");

list.add("B");

list.add("C");

new Thread(() -> {

Iterator<String> itr = list.iterator();

while (itr.hasNext()) {

String str = itr.next();

System.out.println(str);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

new Thread(() -> {

list.remove("C");

}).start();

}

上面的代码中我们通过两个线程同时对List集合进行操作,一个线程遍历,一个线程删除。 如果使用for-each循环,则会抛出ConcurrentModificationException异常,但使用Iterator则不会出现异常,正常执行。可以看出Iterator的确是更加安全的遍历方式。

3. 使用线程安全的集合

如果你确实需要在多线程的情况下对集合进行操作,比如遍历和修改操作,但是不想使用Iterator,可以考虑使用线程安全的集合类如:CopyOnWriteArrayList、ConcurrentHashMap等。这些类都是Java提供的特定为多线程环境下的集合类,它们能够在多线程并发操作时保证数据的安全性。

3.1 CopyOnWriteArrayList的使用方法

下面是使用CopyOnWriteArrayList进行操作的代码示例:

List<String> list = new CopyOnWriteArrayList<>();

list.add("A");

list.add("B");

list.add("C");

new Thread(() -> {

Iterator<String> itr = list.iterator();

while (itr.hasNext()) {

String str = itr.next();

System.out.println(str);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

new Thread(() -> {

list.remove("C");

}).start();

运行上述代码,输出结果是:

A

B

CopyOnWriteArrayList实现了读写分离,每当需要修改数据时,都会重新创建一个集合副本,对副本进行操作,操作完成后用新副本代替旧副本。

3.2 ConcurrentHashMap的使用方法

ConcurrentHashMap是一个线程安全的哈希表集合,它是Java提供的专门针对多线程并发访问的集合类。它可以在多个线程读写的同时保证数据的一致性和正确性。

下面是使用ConcurrentHashMap进行操作的代码示例:

ConcurrentHashMap<String, String> map = new ConcurrentHashMap<>();

map.put("key1", "value1");

map.put("key2", "value2");

map.put("key3", "value3");

new Thread(() -> {

Iterator<String> itr = map.keySet().iterator();

while (itr.hasNext()) {

String key = itr.next();

String value = map.get(key);

System.out.println(key + ":" + value);

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

}

}).start();

new Thread(() -> {

map.remove("key3");

}).start();

运行上述代码,输出结果是:

key1:value1

key3:value3

key2:value2

ConcurrentHashMap使用了锁分段,也就是说,它将整个HashMap分成若干个Segment,每个Segment都是一个独立的哈希表,只对其内部的数据进行同步操作,这样就不会在高并发的情况下造成性能瓶颈。

4. 总结

在Java中,我们常常需要在多线程的情况下进行集合的操作,这时就需要考虑并发问题的解决了。使用Iterator进行遍历是目前最为常用和安全的方式,其次还可以选择线程安全的集合类,比如ConcurrentHashMap和CopyOnWriteArrayList等。 但是,在遇到并发问题时,单纯地选择线程安全集合并不意味着就安全了,必须要根据需求选择最合适的集合,并合理使用并发控制方式。

最后,如果您想了解更多Java并发相关知识,可以查阅Java官方文档或者参考Java高并发编程实战等相关书籍。

后端开发标签