一种隐蔽性较高的Java ConcurrentModificationException异常场景

在使用Iterator遍历容器类的过程中,如果对容器的内容进行增加和删除,就会出现ConcurrentModificationException异常。该异常的分析和解决方案详见博文《Java ConcurrentModificationException 异常分析与解决方案》和《解决ArrayList的ConcurrentModificationException》。本文展示一种隐蔽性较高的ConcurrentModificationException异常场景,并给出解决方案。

代码示例如下:

 1 public class ThreadTest {
 2     private static Map<Pattern, Integer> ITEM_MAP = null; //private static ConcurrentMap<Pattern, Integer> ITEM_MAP = null;
 3     private static int getPortLevel(String portName) {
 4         int level = 0;
 5         if(ITEM_MAP == null) {
 6             ITEM_MAP = new HashMap<Pattern, Integer>();   //ITEM_MAP = new ConcurrentHashMap<Pattern, Integer>();
 7             ITEM_MAP.put(Pattern.compile("^Cpos+|^Pos+"), 1);
 8             System.out.println(""+Thread.currentThread().getName()+": cur="+ITEM_MAP.size());
 9             ITEM_MAP.put(Pattern.compile("^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$"), 2);
10             ITEM_MAP.put(Pattern.compile("^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$"), 3);
11             ITEM_MAP.put(Pattern.compile("^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$"), 4);
12             ITEM_MAP.put(Pattern.compile("^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$"), 5);
13             ITEM_MAP.put(Pattern.compile("^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$"), 6);
14             ITEM_MAP.put(Pattern.compile("^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$"), 7);
15             ITEM_MAP.put(Pattern.compile("^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$"), 8);
16             System.out.println(""+Thread.currentThread().getName()+": Map="+ITEM_MAP); //此句可能抛出ConcurrentModificationException异常
17         }
18
19         //ph1:1353986467,ph2:1100489929,equals=false
20         //System.out.println("ph1:"+Pattern.compile("^Cpos+|^Pos+").hashCode()+",ph2:"+Pattern.compile("^Cpos+|^Pos+").hashCode()+
21         //    ",equals="+(Pattern.compile("^Cpos+|^Pos+").equals(Pattern.compile("^Cpos+|^Pos+"))));
22
23         Iterator<Entry<Pattern, Integer>> iter = ITEM_MAP.entrySet().iterator();
24         System.out.println(""+Thread.currentThread().getName()+": Map size="+ITEM_MAP.size());
25         while(iter.hasNext()) {
26             Entry<Pattern, Integer> entry = iter.next(); //此句可能抛出ConcurrentModificationException异常
27             if(entry.getKey().matcher(portName).find()) {
28                 level = entry.getValue();
29                 break;
30             }
31         }
32         return level;
33     }
34
35     public static void main(String[] args)  {
36         Thread thread1 = new Thread(){
37             @Override
38             public void run() {
39                 System.out.println(""+Thread.currentThread().getName()+", Value="+getPortLevel("400GE1/2/3"));
40             };
41         };
42         Thread thread2 = new Thread(){
43             @Override
44             public void run() {
45                 System.out.println(""+Thread.currentThread().getName()+", Value="+getPortLevel("400GE1/2/3"));
46             };
47         };
48         thread1.start();
49         thread2.start();
50     }
51 }

可见,getPortLevel()内ITEM_MAP的初始化类似懒汉式单例,因此存在多线程问题。在多线程环境下,上述代码运行结果多种多样,例如:

1) 线程0调用getPortLevel()进入if(ITEM_MAP == null)分支,初始化ITEM_MAP。

线程1紧随其后调用getPortLevel(),并跳过初始化分支(map!=null),开始遍历;此时ITEM_MAP内刚插入一个键值对,线程1遍历匹配不到,返回Value=0。
        线程0初始化ITEM_MAP完毕,开始遍历并匹配成功,返回Value=8。

Thread-0: cur=1
Thread-1: Map size=1
Thread-0: Map={^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7, ^Cpos+|^Pos+=1, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2}
Thread-0: Map size=8
Thread-1, Value=0
Thread-0, Value=8

2) 线程0和线程1同时调用getPortLevel()进入if(ITEM_MAP == null)分支,初始化ITEM_MAP。

Pattern.compile()会new一个Pattern对象并返回,而相同模式字符串编译后的Pattern对象hashcode不同且equals返回false,因此会插入"重复"的key(Map size=15)。
        线程0和线程1初始化ITEM_MAP完毕,开始遍历并匹配成功,返回Value=8。

Thread-0: cur=1
Thread-1: cur=1
Thread-0: Map={^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^Cpos+|^Pos+=1, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7}
Thread-1: Map={^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^Cpos+|^Pos+=1, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7}
Thread-1: Map size=15
Thread-0: Map size=15
Thread-0, Value=8
Thread-1, Value=8

注意,HashMap会根据key对象的hashcode和equals方法判断key的重复性。因此,使用HashMap时一般要覆写key对象的hashcode和equals方法并确保其正确性,以免插入“重复”的key。

3) 线程0调用getPortLevel()进入if(ITEM_MAP == null)分支,初始化ITEM_MAP。

线程1紧随其后调用getPortLevel(),并跳过初始化分支(map!=null),开始遍历;此时线程0正在向ITEM_MAP内插入键值对,因遍历期间条目数改变而触发ConcurrentModificationException。
        线程0初始化ITEM_MAP完毕,开始遍历并匹配成功,返回Value=8。

Thread-0: cur=1
Thread-1: Map size=1
Exception in thread "Thread-1" Thread-0: Map={^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7, ^Cpos+|^Pos+=1, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2}
Thread-0: Map size=8
java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(Unknown Source)
	at java.util.HashMap$EntryIterator.next(Unknown Source)
	at java.util.HashMap$EntryIterator.next(Unknown Source)
Thread-0, Value=8

4) 线程0和线程1同时调用getPortLevel()进入if(ITEM_MAP == null)分支,初始化ITEM_MAP。

线程0初始化ITEM_MAP完毕(Map size=11),开始遍历;此时线程1正在向ITEM_MAP内插入键值对,因此线程0触发ConcurrentModificationException。
        线程1初始化ITEM_MAP完毕(Map size=15),开始遍历并匹配成功,返回Value=8。

Thread-0: cur=1
Thread-1: cur=1
Thread-0: Map={^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^Cpos+|^Pos+=1}
Thread-0: Map size=11
Thread-1: Map={^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^Cpos+|^Pos+=1, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7}
Thread-1: Map size=15
Thread-1, Value=8
Exception in thread "Thread-0" java.util.ConcurrentModificationException

注意,两个线程先后执行getPortLevel()内ITEM_MAP = new HashMap<Pattern, Integer>()语句时,后者new出的对象会覆盖前者。此时很可能丢失前者已插入的键值对,导致两个线程打印的MAP条目数不同。

5) 线程0和线程1同时调用getPortLevel()进入if(ITEM_MAP == null)分支,初始化ITEM_MAP。

线程0初始化ITEM_MAP完毕,打印ITEM_MAP内容(内含遍历操作);此时线程1正在向ITEM_MAP内插入键值对,因此线程0触发ConcurrentModificationException。
        线程1初始化ITEM_MAP完毕(Map size=15),开始遍历并匹配成功,返回Value=8。

Thread-0: cur=1
Thread-1: cur=1
Thread-1: Map={^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$=6, ^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$=7, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^Cpos+|^Pos+=1, ^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$=3, ^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$=4, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$=8, ^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$=5, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2, ^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$=2}
Thread-1: Map size=15
Exception in thread "Thread-0" java.util.ConcurrentModificationException
	at java.util.HashMap$HashIterator.nextNode(Unknown Source)
	at java.util.HashMap$EntryIterator.next(Unknown Source)
	at java.util.HashMap$EntryIterator.next(Unknown Source)
	at java.util.AbstractMap.toString(Unknown Source)
	at java.lang.String.valueOf(Unknown Source)
	at java.lang.StringBuilder.append(Unknown Source)
	at ThreadTest.getPortLevel(ThreadTest.java:16)
Thread-1, Value=8

以下提供两种修改方案:

    1. ConcurrentHashMap
    修改点如原代码第2行和第6行注释所示,非常简单。该方案仍存在插入"重复"key的问题,但这并非ConcurrentHashMap本身的缺陷。

2. 将ITEM_MAP的初始化放在static语句块内:

 1 private static final Map<Pattern, Integer> ITEM_MAP = new HashMap<Pattern, Integer>();
 2 static {
 3     ITEM_MAP.put(Pattern.compile("^Cpos+|^Pos+"), 1);
 4     ITEM_MAP.put(Pattern.compile("^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$"), 2);
 5     ITEM_MAP.put(Pattern.compile("^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$"), 3);
 6     ITEM_MAP.put(Pattern.compile("^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$"), 4);
 7     ITEM_MAP.put(Pattern.compile("^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$"), 5);
 8     ITEM_MAP.put(Pattern.compile("^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$"), 6);
 9     ITEM_MAP.put(Pattern.compile("^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$"), 7);
10     ITEM_MAP.put(Pattern.compile("^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$"), 8);
11 }
12 private static int getPortLevel(String portName) {
13     int level = 0;
14
15     Iterator<Entry<Pattern, Integer>> iter = ITEM_MAP.entrySet().iterator();
16     while(iter.hasNext()) {
17         Entry<Pattern, Integer> entry = iter.next();
18         if(entry.getKey().matcher(portName).find()) {
19             level = entry.getValue();
20             break;
21         }
22     }
23     return level;
24 }

或者直接在声明时快速初始化:

 1 private static final Map<Pattern, Integer> ITEM_MAP = new LinkedHashMap<Pattern, Integer>() {
 2     {   //可将常见类型的端口放在MAP前面,遍历时利用LinkedHashMap的有序性提高遍历速度
 3         put(Pattern.compile("^Cpos+|^Pos+"), 1);
 4         put(Pattern.compile("^(GE)([0-9/]+)$|^(GE)([0-9/]+):([0-9/]+)$"), 2);
 5         put(Pattern.compile("^(10GE)([0-9/]+)$|^(10GE)([0-9/]+):([0-9/]+)$"), 3);
 6         put(Pattern.compile("^(40GE)([0-9/]+)$|^(40GE)([0-9/]+):([0-9/]+)$"), 4);
 7         put(Pattern.compile("^(50GE)([0-9/]+)$|^(50GE)([0-9/]+):([0-9/]+)$"), 5);
 8         put(Pattern.compile("^(100GE)([0-9/]+)$|^(100GE)([0-9/]+):([0-9/]+)$"), 6);
 9         put(Pattern.compile("^(200GE)([0-9/]+)$|^(200GE)([0-9/]+):([0-9/]+)$"), 7);
10         put(Pattern.compile("^(400GE)([0-9/]+)$|^(400GE)([0-9/]+):([0-9/]+)$"), 8);
11     }
12 };

注意,采用该方案时,应确保其他地方不会对ITEM_MAP进行增、删操作(仅靠final修饰无法保证这点)。若出现该情况,通常意味着深层次的设计缺陷,而试图在编码层面修复往往适得其反。例如,作者遇到的一种错误的写法示例如下(实际代码很复杂):

 1 private LinkedHashSet<String> nameSet;
 2 public LinkedHashSet<String> getNames() {
 3     System.out.println(""+Thread.currentThread().getName()+", nameSet="+nameSet);
 4
 5     if(CollectionUtils.isEmpty(nameSet)) {
 6         nameSet = new LinkedHashSet<String>();
 7         nameSet.add("Jack");
 8         nameSet.add("Jame");
 9     }
10
11     //return nameSet;
12     //无论是new时以nameSet初始化还是new出对象后调用addAll(nameSet),均可能因为容器内部迭代而触发ConcurrentModificationException。
13     //但本例可保证外部不会直接修改nameSet,所以此处复制对象是安全的。
14     LinkedHashSet<String> names = new LinkedHashSet<String>(nameSet);
15     System.out.println(""+Thread.currentThread().getName()+", names="+names);
16     return names;
17 }
18 public void addNames() {
19     getNames().add("Lucy");
20     getNames().add("Beth");
21 }
22 public void showNames() {
23     for(String name : getNames()) {
24         System.out.println("This is "+name);
25     }
26 }

当线程0调用showNames()的同时,线程1在调用addNames(),就可能导致ConcurrentModificationException异常。当然,本例过于简单,很难真正地触发异常,仅作示例而已。

注意getNames()内复制nameSet对象的写法。该写法试图修复ConcurrentModificationException异常,但因为每次调用都会重新new对象,实际上addNames()无法将Lucy和Beth添加到名字表里!可见,这种试图修复少见异常的尝试反而导致严重的逻辑错误。

原文地址:https://www.cnblogs.com/clover-toeic/p/8462370.html

时间: 2024-08-10 11:00:56

一种隐蔽性较高的Java ConcurrentModificationException异常场景的相关文章

Java并发编程:Java ConcurrentModificationException异常原因和解决方法

Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常.下面我们就来讨论以下这个异常出现的原因以及解决办法. 以下是本文目录大纲: 一.ConcurrentModificationException异常出现的原因 二.在单线程环境下的解决办法 三.在多线程环境下的解决方法 若有不

【转】Java ConcurrentModificationException异常原因和解决方法

原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常.下面我们就来讨论以下这个异常出现的原因以及解决办法. 以下是本文目录大纲: 一.ConcurrentModific

Java ConcurrentModificationException异常问题

今天项目中报了这个异常,到网上查了一下现总结如下: 首页 > 程序开发 > 软件开发 > Java > 正文 Java ConcurrentModificationException 异常分析与解决方案 2014-03-19     我来说两句   来源:Java ConcurrentModificationException 异常分析与解决方案   收藏    我要投稿 一.单线程 1. 异常情况举例 只要抛出出现异常,可以肯定的是代码一定有错误的地方.先来看看都有哪些情况会出现

Java ConcurrentModificationException 异常分析与解决方案

Java ConcurrentModificationException 异常分析与解决方案http://www.2cto.com/kf/201403/286536.html java.util.ConcurrentModificationException 解决办法 http://blog.csdn.net/lipei1220/article/details/9028669 原因:Iterator做遍历的时候,HashMap被修改(bb.remove(ele), size-1),Iterato

(转)Java ConcurrentModificationException异常原因和解决方法

转自 http://www.cnblogs.com/dolphin0520/p/3933551.html 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常.下面我们就来讨论以下这个异常出现的原因以及解决办法. 以下是本文目录大纲: 一.ConcurrentModificationException异常出现的原因 二.在单线程环境下的解决办法 三.在多线程环境下的解

Java ConcurrentModificationException异常原因和解决方法

在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常.下面我们就来讨论以下这个异常出现的原因以及解决办法. 以下是本文目录大纲: 一.ConcurrentModificationException异常出现的原因 二.在单线程环境下的解决办法 三.在多线程环境下的解决方法 若有不正之处请多多谅解,并欢迎批评指正 请尊重作者劳动成果,转载请标明原文链接: http://w

【转】Java ConcurrentModificationException 异常分析与解决方案--还不错

原文网址:http://www.2cto.com/kf/201403/286536.html 一.单线程 1. 异常情况举例 只要抛出出现异常,可以肯定的是代码一定有错误的地方.先来看看都有哪些情况会出现ConcurrentModificationException异常,下面以ArrayList remove 操作进行举例: 使用的数据集合: ? 1 2 3 4 5 6 7 List<string> myList = new ArrayList<string>(); myList.

Java ConcurrentModificationException异常原因和解决方法(转)

摘自:http://www.cnblogs.com/dolphin0520/p/3933551.html#undefined 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.util.ConcurrentModificationException异常.下面我们就来讨论以下这个异常出现的原因以及解决办法. 以下是本文目录大纲: 一.ConcurrentModificationException异常出现的原因 二.在单线程环境下的解决办法 三

java修改集合抛出ConcurrentModificationException异常

测试代码为: public static void main(String[] args) { List<String> strList = new ArrayList<String>(); strList.add("1"); strList.add("2"); strList.add("3"); strList.add("4"); for(String str:strList){ if(str.equ