前言

在使用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。

  1. Thread-0: cur=1
  2. Thread-1: Map size=1
  3. 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}
  4. Thread-0: Map size=8
  5. Thread-1, Value=
  6. Thread-0, Value=

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。

  1. Thread-0: cur=1
  2. Thread-1: cur=1
  3. 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}
  4. 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}
  5. Thread-1: Map size=
  6. Thread-0: Map size=
  7. Thread-0, Value=8
  8. Thread-1, Value=

注意,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。

  1. Thread-0: cur=1
  2. Thread-1: Map size=1
  3. 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}
  4. Thread-0: Map size=8
  5. java.util.ConcurrentModificationException
  6. at java.util.HashMap$HashIterator.nextNode(Unknown Source)
  7. at java.util.HashMap$EntryIterator.next(Unknown Source)
  8. at java.util.HashMap$EntryIterator.next(Unknown Source)
  9. 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。

  1. Thread-0: cur=1
  2. Thread-1: cur=1
  3. 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}
  4. Thread-0: Map size=11
  5. 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}
  6. Thread-1: Map size=15
  7. Thread-1, Value=8
  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。

  1. Thread-0: cur=1
  2. Thread-1: cur=1
  3. 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}
  4. Thread-1: Map size=15
  5. Exception in thread "Thread-0" java.util.ConcurrentModificationException
  6. at java.util.HashMap$HashIterator.nextNode(Unknown Source)
  7. at java.util.HashMap$EntryIterator.next(Unknown Source)
  8. at java.util.HashMap$EntryIterator.next(Unknown Source)
  9. at java.util.AbstractMap.toString(Unknown Source)
  10. at java.lang.String.valueOf(Unknown Source)
  11. at java.lang.StringBuilder.append(Unknown Source)
  12. at ThreadTest.getPortLevel(ThreadTest.java:16)
  13. Thread-1, Value=8

6) 改用ConcurrentHashMap(修改点如原代码第2行和第6行注释所示)后,虽然可消除ConcurrentModificationException异常,但仍存在前述插入"重复"key或某线程漏匹配的问题。

例如:

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

修改方案 

以下提供一种修改方案:

将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添加到名字表里!可见,这种试图修复少见异常的尝试反而导致严重的逻辑错误。

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

  1. Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

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

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

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

    原文网址:http://www.cnblogs.com/dolphin0520/p/3933551.html Java ConcurrentModificationException异常原因和解决方法 ...

  4. 9、Java ConcurrentModificationException异常原因和解决方法

    Java ConcurrentModificationException异常原因和解决方法 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会抛出java.u ...

  5. Java ConcurrentModificationException 异常分析与解决方案

    Java ConcurrentModificationException 异常分析与解决方案http://www.2cto.com/kf/201403/286536.html java.util.Co ...

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

    原文网址:http://www.2cto.com/kf/201403/286536.html 一.单线程 1. 异常情况举例 只要抛出出现异常,可以肯定的是代码一定有错误的地方.先来看看都有哪些情况会 ...

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

    转载自:http://www.cnblogs.com/dolphin0520/p/3933551.html 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同时对其进行修改就会 ...

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

    摘自:http://www.cnblogs.com/dolphin0520/p/3933551.html#undefined 在前面一篇文章中提到,对Vector.ArrayList在迭代的时候如果同 ...

  9. 编写高质量java代码151个建议

    http://blog.csdn.net/aishangyutian12/article/details/52699938 第一章  Java开发中通用的方法和准则 建议1:不要在常量和变量中出现易混 ...

随机推荐

  1. 将两个DataTable合并成一个DataTable

    转载自 http://blog.csdn.net/wangxiaojia42121/article/details/53330464 谢谢 //两个结构一样的DT合并DataTable DataTab ...

  2. Set集合架构和常用实现类的源码分析以及实例应用

    说明:Set的实现类都是基于Map来实现的(HashSet是通过HashMap实现的,TreeSet是通过TreeMap实现的). (01) Set 是继承于Collection的接口.它是一个不允许 ...

  3. Updating and Publishing a NuGet Package - Plus making NuGet packages smarter and avoiding source edits with WebActivator

    I wrote a post a few days ago called "Creating a NuGet Package in 7 easy steps - Plus using NuG ...

  4. unity-Profiler调试Android的正确姿势(mumu模拟器)

    1. 前置条件 安卓的相关环境 java.ant.sdk.ndk 什么的都装好(其实这里只需要 sdk 里面的 adb),配好 adb 工具的环境变量(意思就是 cmd 里直接输 adb 命令即可) ...

  5. .Net转Java.04.踩到switch的坑

    今天线上有个NullPointerException 的异常,我翻了一下代码,抛异常的竟然是switch语句 我有种不祥的预感,本地做了实验 结果是 Java的switch如果传入null值,会抛出  ...

  6. NoSQL简单介绍

    这里介绍一下如今经常使用的NoSQL以及各自的特点. NoSQL是2009年突然发展起来的.如今趋于稳定的状态,市场上也有了一些比較成熟的产品. 传统的关系型数据库为了保证通用性的设计而带来了功能复杂 ...

  7. Github超棒资源汇总

    Awesome List 中文资源大全 经典编程书籍大全 免费的编程中文书籍索引 awesome-awesomeness-zh_CN https://github.com/jnv/lists awes ...

  8. [Algorithm] Fibonacci Sequence - Anatomy of recursion and space complexity analysis

    For Fibonacci Sequence, the space complexity should be the O(logN), which is the height of tree. Che ...

  9. [android警告]AndroidManifest.xml警告 Not targeting the latest versions of Android

    警告:Not targeting the latest versions of Android; compatibility modes apply.Consider testing and upda ...

  10. 采石厂管理系统V3.0版本上线(采石厂车辆出入管理系统,石厂开票系统)

    新版系统包含老版所有功能,软件基础功能请点击查看<采石管理系统,采石厂车辆出入管理系统> 新增功能点 近期对采石厂管理系统进行了升级和完善,系统更加灵活好用,应用场景更加广泛.主要更新一下 ...