java.util.ConcurrentModificationException

java.util.ConcurrentModificationException is a very common exception when working with Java collection classes. Java Collection classes are fail-fast, which means if the Collection will be changed while some thread is traversing over it using iterator, the iterator.next() will throw ConcurrentModificationException.

使用Java集合类时,java.util.ConcurrentModificationException是一个非常常见的异常。 Java Collection类是快速失败的,这意味着如果在使用迭代器遍历某些线程时更改Collection,则iterator.next()将抛出ConcurrentModificationException

Concurrent modification exception can come in the case of multithreaded as well as a single-threaded Java programming environment.

在多线程以及单线程Java编程环境中,可能会发生并发修改异常。

java.util.ConcurrentModificationException (java.util.ConcurrentModificationException)

Let’s see the concurrent modification exception scenario with an example.

让我们看一个并发修改异常场景的例子。

package com.journaldev.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");
			}
		}

	}
}

Above program will throw java.util.ConcurrentModificationException when executed, as shown in below console logs.

上面的程序在执行时将抛出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.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionExample.main(ConcurrentModificationExceptionExample.java:22)

From the output stack trace, it’s clear that the concurrent modification exception is thrown when we call iterator next() function.

从输出堆栈跟踪中可以明显看出,当我们调用iterator next()函数时,将引发并发修改异常。

If you are wondering how Iterator checks for the modification, it’s implementation is present in the AbstractList class, where an int variable modCount is defined. The modCount provides the number of times list size has been changed. The modCount value is used in every next() call to check for any modifications in a function checkForComodification().

如果您想知道Iterator如何检查修改,则它的实现在AbstractList类中提供,其中定义了一个int变量modCount 。 modCount提供更改列表大小的次数。 在每个next()调用中都使用modCount值来检查功能checkForComodification()是否有任何修改。

Now, comment out the list part and run the program again. You will see that there is no ConcurrentModificationException being thrown now.

现在,注释掉列​​表部分,然后再次运行该程序。 您将看到现在没有抛出ConcurrentModificationException。

Output:

输出:

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

Since we are updating the existing key value in the myMap, its size has not been changed and we are not getting ConcurrentModificationException. The output may be different in your system because HashMap keyset is not ordered like a List.

由于我们正在更新myMap中的现有键值,因此其大小尚未更改,并且未获取ConcurrentModificationException 。 在您的系统中,输出可能会有所不同,因为HashMap键集的排序方式与列表不同。

If you will uncomment the statement where I am adding a new key-value in the HashMap, it will cause ConcurrentModificationException.

如果您将在HashMap中添加新键值的语句取消注释,则将导致ConcurrentModificationException。

在多线程环境中避免ConcurrentModificationException (To Avoid ConcurrentModificationException in multi-threaded environment)

  1. You can convert the list to an array and then iterate on the array. This approach works well for small or medium size list but if the list is large then it will affect the performance a lot.

    您可以将列表转换为数组,然后在数组上进行迭代。 这种方法适用于中小型列表,但是如果列表很大,则对性能的影响很大。
  2. You can lock the list while iterating by putting it in a synchronized block. This approach is not recommended because it will cease the benefits of multithreading.

    您可以通过将列表放在同步块中来在锁定时锁定列表。 不建议使用此方法,因为它将停止多线程的好处。
  3. If you are using JDK1.5 or higher then you can use ConcurrentHashMap and CopyOnWriteArrayList classes. This is the recommended approach to avoid concurrent modification exception.

    如果使用的是JDK1.5或更高版本,则可以使用ConcurrentHashMapCopyOnWriteArrayList类。 建议使用此方法来避免并发修改异常。

在单线程环境中避免ConcurrentModificationException (To Avoid ConcurrentModificationException in single-threaded environment)

You can use the iterator remove() function to remove the object from underlying collection object. But in this case, you can remove the same object and not any other object from the list.

您可以使用迭代器remove()函数从基础集合对象中删除该对象。 但是在这种情况下,您可以从列表中删除同一对象,而不能删除任何其他对象。

Let’s run an example using Concurrent Collection classes.

让我们使用并发集合类运行示例。

package com.journaldev.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());
	}

}

The output of the above program is shown below. You can see that there is no ConcurrentModificationException being thrown by the program.

上面程序的输出如下所示。 您可以看到该程序没有引发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

From the above example it’s clear that:

从上面的示例可以清楚地看出:

  1. Concurrent Collection classes can be modified safely, they will not throw ConcurrentModificationException.

    可以安全地修改并发Collection类,它们不会引发ConcurrentModificationException。
  2. In case of CopyOnWriteArrayList, iterator doesn’t accommodate the changes in the list and works on the original list.

    如果使用CopyOnWriteArrayList,则迭代器无法容纳列表中的更改,并且可以处理原始列表。
  3. In case of ConcurrentHashMap, the behaviour is not always the same.For condition:
    if(key.equals("1")){
    	myMap.remove("3");}

    Output is:

    It is taking the new object added with key “4” but not the next added object with key “5”.

    Now if I change the condition to below.

    if(key.equals("3")){
    	myMap.remove("2");}

    Output is:

    In this case, it’s not considering the newly added objects.

    So if you are using ConcurrentHashMap then avoid adding new objects as it can be processed depending on the keyset. Note that the same program can print different values in your system because HashMap keyset is not ordered.

    if(key.equals("1")){
    	myMap.remove("3");}

    输出为:

    它将使用添加了键“ 4”的新对象,而不是下一个添加了键“ 5”的对象。

    现在,如果我将条件更改为以下内容。

    if(key.equals("3")){
    	myMap.remove("2");}

    输出为:

    在这种情况下,它不考虑新添加的对象。

    因此,如果您使用的是ConcurrentHashMap,请避免添加新对象,因为可以根据键集对其进行处理。 请注意,由于未排序HashMap键集,因此同一程序可以在您的系统中打印不同的值。

使用for循环避免java.util.ConcurrentModificationException (Use for loop to avoid java.util.ConcurrentModificationException)

If you are working on single-threaded environment and want your code to take care of the extra added objects in the list then you can do so using for loop rather than an Iterator.

如果您正在单线程环境中工作,并且希望您的代码处理列表中额外添加的对象,则可以使用for循环而不是Iterator来实现

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");
	}
}

Note that I am decreasing the counter because I am removing the same object, if you have to remove the next or further far object then you don’t need to decrease the counter. Try it yourself. 🙂

请注意,由于要删除同一对象,所以要减少计数器,如果必须删除下一个或更远的对象,则不需要减少计数器。 自己尝试。 🙂

One More Thing: You will get ConcurrentModificationException if you will try to modify the structure of the original list with subList. Let’s see this with a simple example.

还有一件事情 :如果尝试使用subList修改原始列表的结构,则将获得ConcurrentModificationException。 让我们用一个简单的例子来看一下。

package com.journaldev.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

	}

}

Output of above program is:

上面程序的输出是:

[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.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionWithArrayListSubList.main(ConcurrentModificationExceptionWithArrayListSubList.java:26)

According to the ArrayList subList documentation, structural modifications is allowed only on the list returned by subList method. All methods on the returned list first check to see if the actual modCount of the backing list is equal to its expected value and throw a ConcurrentModificationException if it is not.

根据ArrayList subList文档,仅对subList方法返回的列表进行结构修改。 返回列表上的所有方法首先检查后备列表的实际modCount是否等于其期望值,如果不是,则抛出ConcurrentModificationException。

GitHub Repository. GitHub Repository下载所有示例代码。

翻译自: https://www.journaldev.com/378/java-util-concurrentmodificationexception

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值