java.util.ConcurrentModificationException 可以用 “java.util.ConcurrentModificationException”来表达

在使用Java集合类时,java.util.ConcurrentModificationException是非常常见的异常。Java集合类是快速失败(fail-fast)的,这意味着如果在使用迭代器遍历集合时,集合被修改,那么调用迭代器的next()方法将抛出ConcurrentModificationException异常。并发修改异常可能会发生在多线程和单线程的Java编程环境中。

Java中的ConcurrentModificationException异常

java.util.ConcurrentModificationException, ConcurrentModificationException, Concurrent Modification Exception, Java ConcurrentModificationException
package com.Olivia.ConcurrentModificationException;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

public class ConcurrentModificationExceptionExample {

	public static void main(String args[]) {
		List<String> myList = new ArrayList<String>();

		myList.add("1");
		myList.add("2");
		myList.add("3");
		myList.add("4");
		myList.add("5");

		Iterator<String> it = myList.iterator();
		while (it.hasNext()) {
			String value = it.next();
			System.out.println("List Value:" + value);
			if (value.equals("3"))
				myList.remove(value);
		}

		Map<String, String> myMap = new HashMap<String, String>();
		myMap.put("1", "1");
		myMap.put("2", "2");
		myMap.put("3", "3");

		Iterator<String> it1 = myMap.keySet().iterator();
		while (it1.hasNext()) {
			String key = it1.next();
			System.out.println("Map Value:" + myMap.get(key));
			if (key.equals("2")) {
				myMap.put("1", "4");
				// myMap.put("4", "4");
			}
		}

	}
}

当执行以上程序时,会抛出java.util.ConcurrentModificationException异常,如下控制台日志所示。

List Value:1
List Value:2
List Value:3
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
	at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
	at com.Olivia.ConcurrentModificationException.ConcurrentModificationExceptionExample.main(ConcurrentModificationExceptionExample.java:22)

从输出的堆栈跟踪来看,明显可以看出当我们调用迭代器的next()函数时会抛出并发修改异常。如果你想知道迭代器如何检测修改,它的实现存在于AbstractList类中,其中定义了一个整数变量modCount。modCount记录了列表大小改变的次数。在每次调用next()函数时,modCount的值被用于检查是否有任何修改,这个检查是在函数checkForComodification()中进行的。现在,注释掉列表部分并再次运行程序,你会发现现在没有抛出ConcurrentModificationException异常了。输出:

Map Value:3
Map Value:2
Map Value:4

由于我们在myMap中更新现有的键值对,myMap的大小没有改变,所以我们没有收到ConcurrentModificationException。输出在您的系统中可能会有所不同,因为HashMap的键集不像列表那样有序。如果您取消注释我在HashMap中添加新键值对的语句,将会引发ConcurrentModificationException。

为避免在多线程环境中出现ConcurrentModificationException

    1. 你可以将列表转换为数组,然后在数组上进行迭代。这种方法适用于小到中等大小的列表,但如果列表很大,则会严重影响性能。

 

    1. 你可以通过将列表放在同步块中来在迭代时锁定列表。这种方法不推荐,因为会取消多线程的好处。

 

    如果你使用的是JDK1.5或更高版本,你可以使用ConcurrentHashMap和CopyOnWriteArrayList类。这是推荐的方法,以避免并发修改异常。

为了在单线程环境中避免 ConcurrentModificationException。

你可以使用迭代器的remove()函数来从底层集合对象中删除对象。但在这种情况下,你只能删除相同的对象,而不能删除列表中的其他对象。让我们使用并发集合类运行一个例子。

package com.Olivia.ConcurrentModificationException;

import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class AvoidConcurrentModificationException {

	public static void main(String[] args) {

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

		myList.add("1");
		myList.add("2");
		myList.add("3");
		myList.add("4");
		myList.add("5");

		Iterator<String> it = myList.iterator();
		while (it.hasNext()) {
			String value = it.next();
			System.out.println("List Value:" + value);
			if (value.equals("3")) {
				myList.remove("4");
				myList.add("6");
				myList.add("7");
			}
		}
		System.out.println("List Size:" + myList.size());

		Map<String, String> myMap = new ConcurrentHashMap<String, String>();
		myMap.put("1", "1");
		myMap.put("2", "2");
		myMap.put("3", "3");

		Iterator<String> it1 = myMap.keySet().iterator();
		while (it1.hasNext()) {
			String key = it1.next();
			System.out.println("Map Value:" + myMap.get(key));
			if (key.equals("1")) {
				myMap.remove("3");
				myMap.put("4", "4");
				myMap.put("5", "5");
			}
		}

		System.out.println("Map Size:" + myMap.size());
	}

}

以上程序的输出如下所示。您可以看到程序没有抛出ConcurrentModificationException的异常。

List Value:1
List Value:2
List Value:3
List Value:4
List Value:5
List Size:6
Map Value:1
Map Value:2
Map Value:4
Map Value:5
Map Size:4

从上面的例子可以看出,

    1. 同时进行的Collection类可以安全地被修改,它们不会抛出ConcurrentModificationException异常。

在CopyOnWriteArrayList的情况下,迭代器不会适应列表中的变化,而是对原始列表进行操作。

在ConcurrentHashMap的情况下,行为并不总是相同。对于条件:
如果(key.equals(“1”)){
myMap.remove(“3”);}

输出结果是:
Map Value:1
Map Value:null
Map Value:4
Map Value:2
Map Size:4

它接受了添加了键”4″的新对象,但没有接受添加了键”5″的下一个对象。现在,如果我将条件更改为以下情况。
如果(key.equals(“3”)){
myMap.remove(“2”);}

输出结果是:
Map Value:1
Map Value:3
Map Value:null
Map Size:4

在这种情况下,它不考虑新添加的对象。所以如果你使用ConcurrentHashMap,避免添加新对象,因为它可能根据键集合进行处理。请注意,相同的程序可能会在您的系统中打印不同的值,因为HashMap的键集合是无序的。

使用for循环来避免java.util.ConcurrentModificationException

如果你在单线程环境中工作,并且希望你的代码能够处理列表中新增加的额外对象,那么你可以使用for循环来实现,而不是使用迭代器。

for(int i = 0; i<myList.size(); i++){
	System.out.println(myList.get(i));
	if(myList.get(i).equals("3")){
		myList.remove(i);
		i--;
		myList.add("6");
	}
}

请注意,我在减少计数器,因为我正在移除相同的对象,如果您需要移除下一个或更远的对象,则无需减少计数器。您可以自行尝试。:) 还有一件事:如果您尝试使用子列表修改原始列表的结构,您将会收到ConcurrentModificationException异常。让我们通过一个简单的例子来看看。

package com.Olivia.ConcurrentModificationException;

import java.util.ArrayList;
import java.util.List;

public class ConcurrentModificationExceptionWithArrayListSubList {

	public static void main(String[] args) {

		List<String> names = new ArrayList<>();
		names.add("Java");
		names.add("PHP");
		names.add("SQL");
		names.add("Angular 2");

		List<String> first2Names = names.subList(0, 2);

		System.out.println(names + " , " + first2Names);

		names.set(1, "JavaScript");
		// check the output below. :)
		System.out.println(names + " , " + first2Names);

		// Let's modify the list size and get ConcurrentModificationException
		names.add("NodeJS");
		System.out.println(names + " , " + first2Names); // this line throws exception

	}

}

上述程序的输出结果是:

[Java, PHP, SQL, Angular 2] , [Java, PHP]
[Java, JavaScript, SQL, Angular 2] , [Java, JavaScript]
Exception in thread "main" java.util.ConcurrentModificationException
	at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1282)
	at java.base/java.util.ArrayList$SubList.listIterator(ArrayList.java:1151)
	at java.base/java.util.AbstractList.listIterator(AbstractList.java:311)
	at java.base/java.util.ArrayList$SubList.iterator(ArrayList.java:1147)
	at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:465)
	at java.base/java.lang.String.valueOf(String.java:2801)
	at java.base/java.lang.StringBuilder.append(StringBuilder.java:135)
	at com.Olivia.ConcurrentModificationException.ConcurrentModificationExceptionWithArrayListSubList.main(ConcurrentModificationExceptionWithArrayListSubList.java:26)

根据ArrayList subList的文档,只允许在subList方法返回的列表上进行结构性修改。返回的列表上的所有方法首先检查备用列表的实际modCount是否等于其预期值,并在不相等时抛出ConcurrentModificationException异常。

您可以从我们的GitHub存储库中下载所有示例代码。

发表回复 0

Your email address will not be published. Required fields are marked *


广告
将在 10 秒后关闭
bannerAds