java.util.ConcurrentModificationException 是使用 Java 收藏类的一个非常常见的例外。Java 收藏类是错误的,这意味着如果收藏在使用 iterator 穿过它时会被更改,则 iterator.next()
会丢弃 ConcurrentModificationException。
java.util.竞争对手修改例外
Let's see the concurrent modification exception scenario with an example.
1package com.journaldev.ConcurrentModificationException;
2
3import java.util.ArrayList;
4import java.util.HashMap;
5import java.util.Iterator;
6import java.util.List;
7import java.util.Map;
8
9public class ConcurrentModificationExceptionExample {
10
11 public static void main(String args[]) {
12 List<String> myList = new ArrayList<String>();
13
14 myList.add("1");
15 myList.add("2");
16 myList.add("3");
17 myList.add("4");
18 myList.add("5");
19
20 Iterator<String> it = myList.iterator();
21 while (it.hasNext()) {
22 String value = it.next();
23 System.out.println("List Value:" + value);
24 if (value.equals("3"))
25 myList.remove(value);
26 }
27
28 Map<String, String> myMap = new HashMap<String, String>();
29 myMap.put("1", "1");
30 myMap.put("2", "2");
31 myMap.put("3", "3");
32
33 Iterator<String> it1 = myMap.keySet().iterator();
34 while (it1.hasNext()) {
35 String key = it1.next();
36 System.out.println("Map Value:" + myMap.get(key));
37 if (key.equals("2")) {
38 myMap.put("1", "4");
39 // myMap.put("4", "4");
40 }
41 }
42
43 }
44}
上面的程序将在执行时投放java.util.ConcurrentModificationException
,如下所示的控制台日志。
1List Value:1
2List Value:2
3List Value:3
4Exception in thread "main" java.util.ConcurrentModificationException
5 at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:937)
6 at java.base/java.util.ArrayList$Itr.next(ArrayList.java:891)
7 at com.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionExample.main(ConcurrentModificationExceptionExample.java:22)
从输出堆栈跟踪,很明显,当我们调用 iterator next()
函数时,同时修改的例外会被扔掉。如果你想知道 iterator 是如何检查修改的,它的实现在 AbstractList 类中存在,其中定义了 int 变量 modCount。 modCount 提供了列表大小被更改的次数。 modCount 值在每个接下来的() 调用中被用来检查函数 checkForComodification()
中的任何更改。
1Map Value:3
2Map Value:2
3Map Value:4
由于我们正在更新 myMap 中的现有关键值,所以它的大小没有更改,我们没有收到 ConcurrentModificationException. 输出可能会在您的系统中有所不同,因为 HashMap键盘不像列表一样排序。
避免在多线性环境中的异常变化
- 您可以将列表转换为数组,然后重复在数组上。 这种方法适用于小或中等大小的列表,但如果列表大,则会影响性能很多
- 您可以在重复过程中将列表锁定,将其放入同步块中。 这种方法不建议,因为它将停止使用多线 3。 如果您使用 JDK1.5 或更高,则可以使用 ConcurrentHashMap和 CopyOnWriteArrayList类。
避免在单线环境中的异常变化
您可以使用 iterator remove()
函数将对象从基础集合对象中删除,但在这种情况下,您可以从列表中删除相同的对象,而不是任何其他对象。
1package com.journaldev.ConcurrentModificationException;
2
3import java.util.Iterator;
4import java.util.List;
5import java.util.Map;
6import java.util.concurrent.ConcurrentHashMap;
7import java.util.concurrent.CopyOnWriteArrayList;
8
9public class AvoidConcurrentModificationException {
10
11 public static void main(String[] args) {
12
13 List<String> myList = new CopyOnWriteArrayList<String>();
14
15 myList.add("1");
16 myList.add("2");
17 myList.add("3");
18 myList.add("4");
19 myList.add("5");
20
21 Iterator<String> it = myList.iterator();
22 while (it.hasNext()) {
23 String value = it.next();
24 System.out.println("List Value:" + value);
25 if (value.equals("3")) {
26 myList.remove("4");
27 myList.add("6");
28 myList.add("7");
29 }
30 }
31 System.out.println("List Size:" + myList.size());
32
33 Map<String, String> myMap = new ConcurrentHashMap<String, String>();
34 myMap.put("1", "1");
35 myMap.put("2", "2");
36 myMap.put("3", "3");
37
38 Iterator<String> it1 = myMap.keySet().iterator();
39 while (it1.hasNext()) {
40 String key = it1.next();
41 System.out.println("Map Value:" + myMap.get(key));
42 if (key.equals("1")) {
43 myMap.remove("3");
44 myMap.put("4", "4");
45 myMap.put("5", "5");
46 }
47 }
48
49 System.out.println("Map Size:" + myMap.size());
50 }
51
52}
上面的程序的输出显示在下方. 你可以看到没有ConcurrentModificationException被程序扔掉。
1List Value:1
2List Value:2
3List Value:3
4List Value:4
5List Value:5
6List Size:6
7Map Value:1
8Map Value:2
9Map Value:4
10Map Value:5
11Map Size:4
从上面的例子看,很明显:
- 联合国 同时期收藏类可以安全地被修改,它们不会抛出同时期的ModizationException.
- 对于CopyOnWriteArrayList,执行者不适应列表中的变动,在原始列表上工作.
- 对于同源HashMap,行为并不总是一样. 条件 : 如果 (key.equals ("1") { myMap.remove ("3")} 输出是 :_ 地图值 : 地图值 : (- ) 地图值 : (- ) 地图值 : (- ) 地图值 : (- ) 地图值 : (- ) 它取用新添加了"4"键的新对象,而不是下一个添加了"5"键的新对象. 现在,如果我将条件更改为下面. { if(key.equers ("3"){ myMap.remove ("2")}} {输出为:_ 地图值: 地图值: 地图值: 地图值: 地图值: 在这种情况下,它不考虑新添加的对象. 因此,如果您正在使用 ConformatHashMap, 那么将避免添加新的对象, 因为它可以根据密钥集进行处理 。 注意同一程序可以在您的系统中打印出不同的值, 因为 HashMap 密钥集没有命令 。 (单位:千美元) (英语)
使用循环以避免 java.util.ConcurrentModification例外
如果您正在使用单线程环境,并希望您的代码来处理列表中的额外添加对象,那么您可以使用循环而不是 Iterator来做到这一点。
1for(int i = 0; i<myList.size(); i++){
2 System.out.println(myList.get(i));
3 if(myList.get(i).equals("3")){
4 myList.remove(i);
5 i--;
6 myList.add("6");
7 }
8}
请注意,我正在减少计数器,因为我正在删除相同的对象,如果你必须删除下一个或更远的对象,那么你不需要减少计数器。
1package com.journaldev.ConcurrentModificationException;
2
3import java.util.ArrayList;
4import java.util.List;
5
6public class ConcurrentModificationExceptionWithArrayListSubList {
7
8 public static void main(String[] args) {
9
10 List<String> names = new ArrayList<>();
11 names.add("Java");
12 names.add("PHP");
13 names.add("SQL");
14 names.add("Angular 2");
15
16 List<String> first2Names = names.subList(0, 2);
17
18 System.out.println(names + " , " + first2Names);
19
20 names.set(1, "JavaScript");
21 // check the output below. :)
22 System.out.println(names + " , " + first2Names);
23
24 // Let's modify the list size and get ConcurrentModificationException
25 names.add("NodeJS");
26 System.out.println(names + " , " + first2Names); // this line throws exception
27
28 }
29
30}
上述方案的结果是:
1[Java, PHP, SQL, Angular 2] , [Java, PHP]
2[Java, JavaScript, SQL, Angular 2] , [Java, JavaScript]
3Exception in thread "main" java.util.ConcurrentModificationException
4 at java.base/java.util.ArrayList$SubList.checkForComodification(ArrayList.java:1282)
5 at java.base/java.util.ArrayList$SubList.listIterator(ArrayList.java:1151)
6 at java.base/java.util.AbstractList.listIterator(AbstractList.java:311)
7 at java.base/java.util.ArrayList$SubList.iterator(ArrayList.java:1147)
8 at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:465)
9 at java.base/java.lang.String.valueOf(String.java:2801)
10 at java.base/java.lang.StringBuilder.append(StringBuilder.java:135)
11 at com.journaldev.ConcurrentModificationException.ConcurrentModificationExceptionWithArrayListSubList.main(ConcurrentModificationExceptionWithArrayListSubList.java:26)
根据 ArrayList subList 文档,仅允许通过 subList 方法返回的列表进行结构更改。
您可以从我们的 GitHub 存储库下载所有示例代码。