天道酬勤,学无止境

【集合类】 1 java.util.ConcurrentModificationException异常详解&ArrayList&CopyOnWriteArrayList原理探究

环境:JDK 1.8.0_111

文章目录

    • 一、单线程情况下问题分析及解决方案
      • 1.1 问题复现
      • 1.2 问题原因分析
      • 1.3 问题解决方案
    • 二、 多线程情况下的问题分析及解决方案
      • 2.1 问题复现
      • 2.2 问题分析
      • 2.3 多线程下的解决方案
        • 2.3.1 方案一:加同步锁
        • 2.3.2 方案二:使用CopyOnWriteArrayList
          • CopyOnWriteArrayList注意事项
    • 三、汇总

在Java开发过程中,使用iterator或for遍历集合的同时对集合进行修改就会出现java.util.ConcurrentModificationException异常,本文就以ArrayList为例去理解和解决这种异常,一般抛出异常的场景分为两类,一是单线程场景,二是多线程场景,尤其是第二个场景不容易察觉,不幸的是小编就中招了。

一、单线程情况下问题分析及解决方案

1.1 问题复现

先上一段会抛异常的代码,iterator方式:

import java.util.ArrayList;
import java.util.Iterator;

public class ExceptionTest1 {
    public void test()  {
        ArrayList<Integer> arrayList = new ArrayList<Integer>(); //构建数组,并填充20个元素
        for (int i = 0; i < 20; i++) {
            arrayList.add(Integer.valueOf(i));
        }

        Iterator<Integer> iterator = arrayList.iterator();
        while (iterator.hasNext()) {      //使用iterator迭代
            Integer integer = iterator.next();      //报错的地方  
            if (integer.intValue() == 5) {
                arrayList.remove(integer);  //删除操作,注意调用的是数组的remove,不是iterator的remove
            }
        }
    }
    public static void main(String[] args) {
        ExceptionTest1 test = new ExceptionTest1();
        test.test();
    }
}

上面代码报错的地方不是remove(),而是next()

上面的代码是很常见的方式,即循环体中操作数据,新增或删除都会触发同样的异常,在for循环体中也会复现:

 for( Integer integer  :arrayList){ 
   if(integer.intValue() == 5){   //报错的地方 
          arrayList.remove(integer);  //删除操作
      }
  }

for(元素类型t 元素变量x : 遍历对象obj)语法底层调用foreach(),和for(int i =0;i<10;i++)不同

1.2 问题原因分析

使用Iterator()遍历ArrayList, 抛出异常的是iterator.next()。看下Iterator next()方法实现源码

//java.util.ArrayList.Itr

public E next() {
   checkForComodification();
    int i = cursor;
    if (i >= size)
        throw new NoSuchElementException();
    Object[] elementData = ArrayList.this.elementData;
    if (i >= elementData.length)
        throw new ConcurrentModificationException();
    cursor = i + 1;
    return (E) elementData[lastRet = i];
}

final void checkForComodification() {
     if (modCount != expectedModCount)  //`原因所在,该方法会判断modCount 是否等于 expectedModCount`
         throw new ConcurrentModificationException();
 }

在next()方法中首先调用了checkForComodification方法,该方法会判断modCount是否等于expectedModCount,不等于就会抛出java.util.ConcurrentModificationExcepiton异常。

我们接下来跟踪看一下modCountexpectedModCount的赋值和修改。

modCount是ArrayList的一个属性,继承自抽象类AbstractList,用于表示ArrayList对象被修改次数

 protected transient int modCount = 0;

整个ArrayList中修改modCount的方法比较多,有addremoveclearensureCapacityInternal等,凡是设计到ArrayList对象修改的都会自增modCount属性。

在创建Iterator的时候会将modCount赋值给expectedModCount,在遍历ArrayList过程中,没有其他地方可以设置expectedModCount了,因此遍历过程中expectedModCount一直保持初始值20(调用add方法添加了20个元素,修改了20次)

final void checkForComodification() {
    if (modCount != expectedModCount)
        throw new ConcurrentModificationException();
}

在执行next方法时,遇到modCount != expectedModCount方法,导致抛出异常java.util.ConcurrentModificationException

明白了抛出异常的过程,但是为什么要这么做呢?很明显这么做是为了阻止程序员在不允许修改的时候修改对象,起到保护作用,避免出现未知异常。

Iterator 是工作在一个独立的线程中,并且拥有一个 mutex 锁。 Iterator 被创建之后会建立一个指向原来对象的单链索引表,当原来的对象数量发生变化时,这个索引表的内容不会同步改变。

当索引指针往后移动的时候就找不到要迭代的对象,所以按照 fail-fast 原则 Iterator 会马上抛出 java.util.ConcurrentModificationException 异常。
所以 Iterator 在工作的时候是不允许被迭代的对象被改变的。但你可以使用 Iterator 本身的方法 remove() 来删除对象, Iterator.remove() 方法会在删除当前迭代对象的同时维护索引的一致性。

有关fail-fast更详细信息请参考《java中快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?》

再来分析下第二种for循环抛异常的原因:

//java.util.ArrayList

public void forEach(Consumer<? super E> action) {
    Objects.requireNonNull(action);
    final int expectedModCount = modCount;
    @SuppressWarnings("unchecked")
    final E[] elementData = (E[]) this.elementData;
    final int size = this.size;
    for (int i=0; modCount == expectedModCount && i < size; i++) {
        action.accept(elementData[i]);
    }
    if (modCount != expectedModCount) {
        throw new ConcurrentModificationException();
    }
}

在for循环中一开始也是对expectedModCount采用modCount进行赋值。在进行for循环时每次都会有判定条件modCount == expectedModCount,当执行完arrayList.remove(integer)之后,该判定条件返回false退出循环,然后执行if语句,结果同样抛出java.util.ConcurrentModificationException异常。

这两种复现方法实际上都是同一个原因导致的

1.3 问题解决方案

上述的两种复现方法都是在单线程运行的,先来说明单线程中的解决方案:

public void test2() {
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            arrayList.add(Integer.valueOf(i));
        }

        Iterator<Integer> iterator = arrayList.iterator();
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            if (integer.intValue() == 5) {
                iterator.remove();  //调用iterator.remove()
            }
        }
    }

这种解决方案最核心的就是调用iterator.remove()方法。我们看看该方法源码为什么这个方法能避免抛出异常

//java.util.ArrayList.Itr

public void remove() {
       if (lastRet < 0)
           throw new IllegalStateException();
       checkForComodification();

       try {
           ArrayList.this.remove(lastRet);
           cursor = lastRet;
           lastRet = -1;
           expectedModCount = modCount;   //`核心,重置了expectedModCount值`
       } catch (IndexOutOfBoundsException ex) {
           throw new ConcurrentModificationException();
       }
   }

iterator.remove()方法中,同样调用了ArrayList自身的remove方法,但是调用完之后并非就return了,而是expectedModCount = modCount重置了expectedModCount值,使二者的值继续保持相等。

针对forEach循环并没有修复方案,因此在遍历过程中同时需要修改ArrayList对象,则需要采用iterator遍历。

上面提出的解决方案调用的是iterator.remove()方法,如果不仅仅是想调用remove方法移除元素,还想增加元素,或者替换元素,是否可以呢?浏览Iterator源码可以发现这是不行的,Iterator只提供了remove方法。

但是ArrayList实现了ListIterator接口,ListIterator类继承了Iter,这些操作都是可以实现的,使用示例如下:

public void test3() {
        ArrayList<Integer> arrayList = new ArrayList<>();
        for (int i = 0; i < 20; i++) {
            arrayList.add(Integer.valueOf(i));
        }

        ListIterator<Integer> iterator = arrayList.listIterator(); //`注意此处是listIterator(),不是iterator()`
        while (iterator.hasNext()) {
            Integer integer = iterator.next();
            if (integer.intValue() == 5) {
                iterator.set(Integer.valueOf(6));
                iterator.remove();
                iterator.add(integer);
            }
        }
    }

iterator()是集合类接口定义的方法,而listIterator()是数组定义的方法,详细区别可以参考ArrayList类的Iterator()和ListIterator()的区别是什么

二、 多线程情况下的问题分析及解决方案

2.1 问题复现

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

public class ScheduleExecutorServiceTest {
    public static void main(String[] args) {
        
        //构建数组,有20个数据
       final List list =new ArrayList();
        for(int i=0;i<20;i++){
            list.add(i);
        }
       //启动一个线程,3s后删除下标20的数据
        Thread thread = new Thread( new Runnable() {
            public void run() {
                try {
                    Thread.sleep(3000);
                    list.remove(10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });
   
        thread.start();
        //主循环任务,仅打印数据,间隔1s
        Iterator it =  list.iterator();
        while (it.hasNext()){
            try { 
                Object o = it.next();   //报错
                System.out.println(o);
                Thread.sleep(1000);
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
        }  
    }   
}

这段代码是main主线程循序数组并打印,新起的thread负责在main线程运行期间触发一次删除操作,我们看下执行结果:

0
1
2
Exception in thread "main" java.util.ConcurrentModificationException
	at java.util.ArrayList$Itr.checkForComodification(ArrayList.java:909)
	at java.util.ArrayList$Itr.next(ArrayList.java:859)
	at ScheduleExecutor.ScheduleExecutorServiceTest.main(ScheduleExecutorServiceTest.java:32)

2.2 问题分析

从上面代码执行结果可以看出,thread在3s删除一条数据后,main线程已经遍历完3条数据,正准备遍历第4个元素,next的时候抛出异常了。我们从时间点分析一下抛异常的原因:

时间点main线程modCountmain线程expectedModCount
remove之前2020
remove之后2120

很明显,main线程的modCount 被修改了,而expectedModCount 没变导致 出错

2.3 多线程下的解决方案

2.3.1 方案一:加同步锁

iterator遍历过程加同步锁,锁住整个arrayList

 //启动一个线程,3s后删除下标20的数据
   Thread thread = new Thread( new Runnable() {
       public void run() {
           try {
               Thread.sleep(3000);
               //`此处必须也加锁`
               synchronized (list) {
                   
                   list.remove(10);
               }
           } catch (InterruptedException e) {
               e.printStackTrace();
           }
       }
   });

   thread.start();
   //`加锁`
   synchronized (list) {
       Iterator it =  list.iterator();
       while (it.hasNext()){
           try { 
               Object o = it.next();
               System.out.println(o);
               Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
       }
       
   }

虽然通过加锁解决了报错信息,但是主线程加锁导致子线程的删除操作一直阻塞,因为删除操作必须在main线程释放锁之后才能完成

上面加锁代码可以稍微优化一下,考虑提升删除操作的效率,给主线程加锁,保证主线程先获取一份拷贝。

 //循环任务,仅打印数据,间隔1s
 Iterator it ;
  synchronized (list) {   //仍然需要加锁
  //`弱拷贝了一份`
  final List list2= new ArrayList(list);
   it =  list2.iterator();
  }
  while (it.hasNext()){
        try { 
            Object o = it.next();
            System.out.println(o);
            Thread.sleep(1000);
      } catch (InterruptedException e) {
          e.printStackTrace();
      }
    }
      
  }

上面优化方案只是弱拷贝了一份数组出来,由于main循环体在新对象上,因此和thead线程各自维护自身的modCount,thead线程的修改操作不影响main线程。

2.3.2 方案二:使用CopyOnWriteArrayList

使用CopyOnWriteArrayList解决读写冲突问题,那么我们通过源码来分析,读写方法为何不会冲突。

get方法:

 public E get(int index) {
      return get(getArray(), index);
  }
  @SuppressWarnings("unchecked")
 private E get(Object[] a, int index) {    //很简单,没有加锁
     return (E) a[index];    
 }

整个读的过程没有添加任何锁,就是普通的数组获取。

2、add方法

private transient volatile Object[] array;  //array内部维护一个数组

public boolean add(E e) {
  final ReentrantLock lock = this.lock;
  lock.lock();
  try {
      Object[] elements = getArray();
      int len = elements.length;
      //`数组复制了一份,最后setArray回去,这是不干扰其他线程读操作的根本原因`
      Object[] newElements = Arrays.copyOf(elements, len + 1);  
      newElements[len] = e;
      setArray(newElements);   //调用setArray,重置数组
      return true;
  } finally {
      lock.unlock();
  }
}

final void setArray(Object[] a) {
        array = a;    //给数组重新赋值
    }

写操作添加了一个锁ReentrantLock,保证了写操作线程安全,读写分离,假设当前数组是A,写操作时复制出一个新的数组B,插入、修改或者移除操作均发生在B上,完成后将新数组赋值给array,期间用户读取A,循环操作也是对A的操作,A和B是两个不同对象,因此读写是分离的,for循环也能正确运行。

下面我们来验证下读写分离:

我们建了2个线程,分别为thread1 和 thread2:

  • thread2
    thread2内会循环打印2次数组数据,第一次循环打印的过程中发生一个删除操作,通过查看第一次打印内容发现,虽然循环体发生了删除操作,但是仍正常工作,并且后续的元素都能读取到,与前面的例子对比,明显解决了报错问题;thread2的第二次打印内容显示此时读到的数组是新数组,不包含已经被删除的元素。
  • thread1
    thread1比thead2先读到数组并打印第一个元素,随后sleep,等结束后,再打印其余元素,在sleep过程中,原始数组中值为3的元素被删除。输出结果中仍然包含3,说明删除操作对读取无影响。
import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class Test2 {
    public static void main(String[] args) {
        final List<Integer> list = new CopyOnWriteArrayList<Integer>();
        for (int i = 0; i < 6; i++) {
            list.add(Integer.valueOf(i));
        }
        // 子线程1
        Thread thread1 = new Thread(new Runnable() {
            public void run() {
                ListIterator<Integer> iterator = list.listIterator();
                while (iterator.hasNext()) { // 使用iterator循环打印
                    System.out.println("thread1 " + iterator.next().intValue());
                    try {
                        Thread.sleep(2000);   //sleep 2s,确保顺序,先让thread2执行删除元素的操作
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        // 子线程2
        Thread thread2 = new Thread(new Runnable() {
             try {
                    Thread.sleep(1000);     //sleep 1s,确保thread1先读到数组
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            public void run() {
                for (Integer integer : list) {
                    System.out.println("thread2 " + integer.intValue());
                    if (integer.intValue() == 3) { //` for循环删除元素值为3的元素,注意,很重要,下文会讲到不能在iterator中执行remove`
                        list.remove(integer);
                    }
                }
                for (Integer integer : list) { // 使用for循环打印,和thread1有所不同
                    System.out.println("thread2 again " + integer.intValue());
                }
            }
        });
        thread1.start();
        thread2.start();
    }

}

执行结果:

thread1 0   //thread1先读取数组,并sleep
thread2 0
thread2 1
thread2 2
thread2 3    //未删除前,线程2第一次打印`包含3`
thread2 4
thread2 5
thread2 again 0
thread2 again 1
thread2 again 2   //删除后,线程2第二次打印`不包含3`
thread2 again 4
thread2 again 5
thread1 1
thread1 2
thread1 3    //虽然发生了删除,thead1仍然打印出3
thread1 4
thread1 5

我们先分析thread2的输出结果,第一次遍历输出 3 ,情理之中;第一次遍历后删除掉了一个元素,第二次遍历输出不包含 3,符合我们的预期。

再来看下thread1的输出结果,thread1 仍然输出了3
果然是读写分离,互不影响。

这是什么原因保证神奇的效果呢,我们看下源码:

private transient volatile Object[] array;  //内部持有一个数组对象

CopyOnWriteArrayList本质上是对array数组的一个封装,一旦CopyOnWriteArrayList对象发生任何的修改都会new一个新的Object[]数组newElement,在newElement数组上执行修改操作,修改完成后将newElement赋值给array数组(array=newElement)。

因为array是volatile的,因此它的修改对所有线程都可见。

了解了CopyOnWriteArrayList的实现思路之后,我们再来分析上面代码为什么会出现那样的输出结果。先来看下thread1和thread2中用到的两种遍历方式的源码。

时间点CopyOnWriteArrayList的arraythread1 遍历Object数组thread2 第一次遍历Object数组thread2 第二次遍历Object数组
thread2 调用remove方法前A (初始数组)AA/
thread2 调用remove方法之后B (setArray(newElements)赋值新产生的数组)A/B

有了这个时间节点表就很清楚了,thread1和thread2 启动的时候都会将A数组初始化给自己的临时变量,之后遍历的也都是这个A数组,而不管CopyOnWriteArrayList中的array发生了什么变化。因此也就解释了thread1在thread2 remove掉一个元素之后为什么还会输出3了。在thread2中,第二次遍历初始化数组变成了当前的array,也就是修改后的B,因此不会有3这个元素了。

执行结果来看,CopyOnWriteArrayList确实能解决一边遍历一边修改并且还不会抛异常,但是这也是有代价的:

  • 不能保证数据的实时一致性,thread2对array数组的修改thread1并不能被动感知到

  • 内存占用问题 ,每次修改都需要重新new一个数组,并且将array数组数据拷贝到new出来的数组中,效率会大幅下降

CopyOnWriteArrayList注意事项

注意:CopyOnWriteArrayList中的ListIterator实现是不支持removeaddset操作的,一旦调用就会抛出UnsupportedOperationException异常,这是一个RuntimeException,可以参见下面的Test3;

public class CopyOnWriteArrayList {
    public Iterator<E> iterator() {    //间接继承自java.lang.Iterable接口
        return new COWIterator<E>(getArray(), 0);
    }
	public ListIterator<E> listIterator() {   //继承自java.util.List接口
	        return new COWIterator<E>(getArray(), 0);
	    }
	
	//内部类    
	static final class COWIterator<E> implements ListIterator<E> {
	    /** Snapshot of the array */
	    private final Object[] snapshot;
	    public void remove() {
	        // `已经不支持Iterator remove操作了`
	        throw new UnsupportedOperationException();
	    }

看个调用Iterator.remove报错的例子:

package mychild;

import java.util.List;
import java.util.ListIterator;
import java.util.concurrent.CopyOnWriteArrayList;

public class Test3 {
    public static void main(String[] args) {
        final List<Integer> list = new CopyOnWriteArrayList<Integer>();
        for (int i = 0; i < 6; i++) {
            list.add(Integer.valueOf(i));
        }

        ListIterator<Integer> iterator = list.listIterator();
        while (iterator.hasNext()) {
            Integer value = iterator.next();
            System.out.println(value);
            if (value == 3) {
                iterator.remove();// `使用iterator.remove删除,报错`
            }
        }

    }
}

为什么不能删除呢?

因为读写分离的机制,假设当前数组是A,写操作时复制出一个新的数组B,插入、修改或者移除操作均发生在B上,完成后将新数组赋值给array,期间用户读取A,循环操作也是对A的操作,A此时已经是个快照了,你即使删除了快照,也只是删除了快照里的对象,不影响array,因此避免出现删除失效的情况,直接禁止调用remove、add和set。

那么想删除怎么办?

只能用list.remove()形式删除

三、汇总

  1. ArrayList是非线程安全的
    原因是未加锁

  2. ArrayList在迭代期间,可能引起ConcurrentModificationException异常
    原因是如果数组发生删除操作,Iterator迭代器在迭代期间会进行modCount != expectedModCount比较,导致抛出异常java.util.ConcurrentModificationException,作用是提醒用户发生了数据不一致的情况,请用户捕获处理

  3. 可以使用CopyOnWriteArrayList替代ArrayList
    作用是正在迭代的数组不会产生异常ConcurrentModificationException,原因是读操作是个备份机制,别的操作不影响当前的快照;因为快照机制,产生了弱一致性问题,即数组发生变化,不会立即体现在快照数组中

  4. CopyOnWriteArrayList也有缺陷,不能在迭代期间调用remove,add操作
    原因是读操作是对快照进行的,如果在迭代期间调用remove(),只会删除快照里面的数据,而不会影响原数组,故而直接禁止调用相关的方法。

  5. 由于采用复制原理,CopyOnWriteArrayList适用于读多写少的情况,否则大量的复制操作,会导致频繁的gc

受限制的 HTML

  • 允许的HTML标签:<a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • 自动断行和分段。
  • 网页和电子邮件地址自动转换为链接。

相关推荐
  • 为什么会引发ConcurrentModificationException以及如何对其进行调试(Why is a ConcurrentModificationException thrown and how to debug it)
    问题 我使用的是Collection (JPA间接使用的HashMap ,它确实发生了),但是显然随机代码抛出ConcurrentModificationException 。 是什么原因引起的,如何解决此问题? 通过使用一些同步,也许吗? 这是完整的堆栈跟踪: Exception in thread "pool-1-thread-1" java.util.ConcurrentModificationException at java.util.HashMap$HashIterator.nextEntry(Unknown Source) at java.util.HashMap$ValueIterator.next(Unknown Source) at org.hibernate.collection.AbstractPersistentCollection$IteratorProxy.next(AbstractPersistentCollection.java:555) at org.hibernate.engine.Cascade.cascadeCollectionElements(Cascade.java:296) at org.hibernate.engine.Cascade.cascadeCollection(Cascade.java:242) at org
  • 如何解决此错误java.util.ConcurrentModificationException(How can I fix this error java.util.ConcurrentModificationException)
    问题 我在下一行出现错误。 我正在做添加到jsonarray的过程。 请帮我。 jsonArr=new JSONArray(); if(req.getSession().getAttribute("userses")!=null){ String name=(req.getParameter("name")==null?"":to_EnglishName(req.getParameter("name").toUpperCase())); if(!name.equals("")){ for(Book c:GlobalObjects.bookList){ if(c.getBookName().startsWith(name)){ jsonObjec=new JSONObject(); jsonObjec.put("label",c.getBookName()); jsonObjec.put("value", c.getId()); jsonArr.add(jsonObjec);//java.util.ConcurrentModificationException } } } } jsonArr.write(res.getWriter()); 回答1 这是我在重新编程时经常遇到的错误。 此异常的原因或细节非常清楚。 不允许在迭代时修改集合(您要添加一个新元素)。
  • java foreach循环 异常java.util.ConcurrentModificationException fail-safe
    说明 java的for语句增强 是java给我们提供的语法糖 ,原理是使用了迭代器 Iterator 所以for增强 可以看成是Iterator迭代器遍历 基于jdk1.7 测试:package com.test;import java.util.ArrayList;import java.util.List;public class MyTest {public static void main(String[] args) throws InterruptedException { List<String> lists = new ArrayList<>(); lists.add("test1"); lists.add("test2"); lists.add("test3"); lists.add("test4");for (String k : lists) {if ("test2".equals(k)) { lists.remove(k);}}/* * Iterator<String> it = lists.iterator(); while (it.hasNext()) { String k = * it.next(); if ("测试2".equals(k)) { lists.remove(k); } } */}}生成的class 进行反编译 反编译工具使用的是jd
  • 了解Java集合中的快速失败机制(fail-fast)
    所有知识体系文章,GitHub已收录,欢迎Star!再次感谢,愿你早日进入大厂! GitHub地址: https://github.com/Ziphtracks/JavaLearningmanual 搜索关注微信公众号“码出Offer”,送你学习福利资源! 最近我也在翻看一些源代码,从头到尾的看了一下HashMap的底层发现了一个这个东西——快速失败机制(fail-fast),以此作为记录! 首先,fail-fast 机制是java集合(Collection)中的一种错误机制。 当多个线程对同一个集合的内容进行操作时,就可能会产生fail-fast事件。 要了解fail-fast机制,我们首先要对ConcurrentModificationException 异常有所了解。当方法检测到对象的并发修改,但不允许这种修改时就抛出该异常。同时需要注意的是,该异常不会始终指出对象已经由不同线程并发修改,如果单线程违反了规则,同样也有可能会抛出该异常。 注意: 迭代器的快速失败行为无法得到保证,它不能保证一定会出现该错误,但是快速失败操作会尽最大努力抛出ConcurrentModificationException异常,所以因此,为提高此类操作的正确性而编写一个依赖于此异常的程序是错误的做法,ConcurrentModificationException 应该多用于检测 bug。
  • 盘点这些年Java 集合类 List 的那些坑
    现在的一些高级编程语言都会提供各种开箱即用的数据结构的实现,像 Java 编程语言的集合框架中就提供了各种实现,集合类包含 Map 和 Collection 两个大类,其中 Collection 下面的 List 列表是我们经常使用的集合类之一,很多的业务代码都离不开它,今天就来看看 List 列表的一些坑。第一个坑:Arrays.asList 方法返回的 List 不支持增加、删除操作例如我们执行以下代码:List<String> strings = Arrays.asList("m", "g"); strings.add("h");复制代码会抛出 java.lang.UnsupportedOperationException 异常,此时你内心 OS what?明明返回的 ArrayList 为啥不能往里面增加元素,这以后还能好好的增加元素吗?,然后果断开启 Debug大法:发现返回的 ArrayList 并不是我们常用的 java.util.ArrayList,而是 Arrays 的内部类 java.util.Arrays.ArrayList。进入方法 Arrays.asList 源码如下:public static <T> List<T> asList(T... a) { return new ArrayList<>(a); }复制代码方法返回的是 Arrays 的静态内部类
  • 面试题思考:java中快速失败(fail-fast)和安全失败(fail-safe)的区别是什么?
    https://www.cnblogs.com/songanwei/p/9387745.html?tdsourcetag=s_pctim_aiomsg 一:快速失败(fail—fast) 在用迭代器(Iterator it = list.iterator();)遍历一个集合对象时,是直接对原集合进行遍历,如果遍历过程中对集合对象的内容进行了修改(增加、删除、修改),则会抛出Concurrent Modification Exception。 原理:迭代器在遍历时直接访问集合中的内容,并且在遍历过程中使用一个 modCount 变量。集合在被遍历期间如果内容发生变化,就会改变modCount的值。每当迭代器使用hashNext()/next()遍历下一个元素之前,都会检测modCount变量是否为expectedmodCount值,是的话就返回遍历;否则抛出异常,终止遍历。 注意:这里异常的抛出条件是检测到 modCount!=expectedmodCount 这个条件。如果集合发生变化时修改modCount值刚好又设置为了expectedmodCount值,则异常不会抛出。因此,不能依赖于这个异常是否抛出而进行并发操作的编程,这个异常只建议用于检测并发修改的bug。 场景:java.util包下的集合类都是快速失败的,不能在多线程下发生并发修改(迭代过程中被修改)。
  • 带有 LinkedHashMap 的 ConcurrentModificationException(ConcurrentModificationException with LinkedHashMap)
    问题 当我遍历下面代码中的LinkedHashMap结构时,不确定是什么触发了java.util.ConcurrentModificationException 。 使用Map.Entry方法工作正常。 没有从以前的帖子中得到关于是什么触发这个的很好的解释。 任何帮助,将不胜感激。 import java.util.LinkedHashMap; import java.util.Map; public class LRU { // private Map<String,Integer> m = new HashMap<String,Integer>(); // private SortedMap<String,Integer> lru_cache = Collections.synchronizedSortedMap(new TreeMap<String, Integer>()); private static final int MAX_SIZE = 3; private LinkedHashMap<String,Integer> lru_cache = new LinkedHashMap<String,Integer>(MAX_SIZE, 0.1F, true){ @Override protected boolean removeEldestEntry(Map.Entry
  • 带有迭代器的java.util.ConcurrentModificationException(java.util.ConcurrentModificationException with iterator)
    问题 我知道是否要尝试通过简单循环将其从集合循环中删除,我将收到此异常: java.util.ConcurrentModificationException 。 但是我正在使用Iterator,它仍然会产生此异常。 知道为什么以及如何解决吗? HashSet<TableRecord> tableRecords = new HashSet<>(); ... for (Iterator<TableRecord> iterator = tableRecords.iterator(); iterator.hasNext(); ) { TableRecord record = iterator.next(); if (record.getDependency() == null) { for (Iterator<TableRecord> dependencyIt = tableRecords.iterator(); dependencyIt.hasNext(); ) { TableRecord dependency = dependencyIt.next(); //Here is the line which throws this exception if (dependency.getDependency() != null && dependency.getDependency()
  • List 抛出 ConcurrentModificationException 但 set 不抛出 ConcurrentModificationException? [复制](List throws ConcurrentModificationException but set does not throws ConcurrentModificationException? [duplicate])
    问题 这个问题在这里已经有了答案: 为什么抛出 ConcurrentModificationException 以及如何调试它8 个回答 2年前关闭。 我有以下两个java类 import java.util.*; public class ArrayListTest032 { public static void main(String[] ar) { List<String> list = new ArrayList<String>(); list.add("core java"); list.add("php"); list.add("j2ee"); list.add("struts"); list.add("hibernate"); Iterator<String> itr = list.iterator(); while (itr.hasNext()) { System.out.println(itr.next()); } list.remove("php"); while (itr.hasNext()) { System.out.println(itr.next()); } } } 当我运行上面的代码时,我得到下面的输出。 core java php j2ee struts hibernate Exception in thread "main" java.util
  • 在ArrayList中的foreach循环内添加时出现ConcurrentModificationException(ConcurrentModificationException when adding inside a foreach loop in ArrayList [duplicate])
    问题 这个问题已经在这里有了答案: 并发修改异常:添加到ArrayList中(10个答案) 5年前关闭。 我正在尝试通过数组列表使用foreach循环,但是当我使用它时,它给了我错误,但是当我使用普通的for循环时,它完美地工作了,这可能是什么问题? 代码在这里: for (Pair p2 : R) { if ((p2.getFirstElm() == p.getSecondElm()) && (p2.getFirstElm() != p2.getSecondElm())) R.add(new Pair (p.getFirstElm(), p2.getSecondElm())); else if ((p2.getSecondElm() == p.getFirstElm()) && (p2.getFirstElm() != p2.getSecondElm())) R.add(new Pair (p2.getFirstElm(), p.getSecondElm())); // else // There are no transitive pairs in R. } 这是无效的循环,这是有效的循环: for (int i = 0; i < R.size(); i++) { if ((R.get(i).getFirstElm() == p.getSecondElm()) && (R
  • 集合根据集合的内容引发或不引发ConcurrentModificationException(Collection throws or doesn't throw ConcurrentModificationException based on the contents of the Collection [duplicate])
    问题 这个问题已经在这里有了答案: 为什么会引发ConcurrentModificationException以及如何对其进行调试(8个答案) 预期时未引发java.util.ConcurrentModificationException (2个答案) 2年前关闭。 以下Java代码按预期引发ConcurrentModificationException : public class Evil { public static void main(String[] args) { Collection<String> c = new ArrayList<String>(); c.add("lalala"); c.add("sososo"); c.add("ahaaha"); removeLalala(c); System.err.println(c); } private static void removeLalala(Collection<String> c) { for (Iterator<String> i = c.iterator(); i.hasNext();) { String s = i.next(); if(s.equals("lalala")) { c.remove(s); } } } } 但是下面的示例仅在Collection的内容上有所不同,但没有例外地执行:
  • 多线程并发变量问题
    背景 ​ 在Gnss功耗优化的项目中我们需要给其他模块提供一个接口,即我们Imp类中的功耗信息,但是我们功耗信息会在每次上传之后就进行了删除操作,因此就出现了多线程并发操作变量的情况,至此记录如下; 情景再现 ​ 我模拟了一下项目中会出现的情况代码如下: public static List<Integer> list = new ArrayList(); public static void main(String[] args) { for (int i = 0; i < 100000; i++) { list.add(i); } System.out.println("list.toString()"); Thread t1 = new Thread(() -> { while (true) { System.out.println("AAA----is running"); try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } // System.out.println(list.toString() + "\n"); } }); t1.setName("AAAAA"); t1.start(); Thread t2 = new Thread(() -> {
  • Java-线程“ main”中的异常java.util.ConcurrentModificationException(Java - Exception in thread “main” java.util.ConcurrentModificationException)
    问题 有什么方法可以在迭代特定键时修改其HashMap值? 下面给出了一个示例程序: public static void main(String[] args) { HashMap<Integer,ArrayList<String>> hm = new HashMap<Integer, ArrayList<String>>(); ArrayList<String> ar = new ArrayList<String>(); for(int i=0;i<50;i++){ ar.add(Integer.toString(i)); } hm.put(1, ar); for(String s:hm.get(1)){ hm.get(1).add("hello"); } } 引发错误: Exception in thread "main" java.util.ConcurrentModificationException at java.util.ArrayList$Itr.checkForComodification(Unknown Source) at java.util.ArrayList$Itr.next(Unknown Source) at Excp.main(Excp.java:17) 回答1 当不允许对对象进行并发修改时,检测到该对象的并发修改的方法可能会引发此异常。
  • 我不断收到 java.util.concurrentmodificationexception .. 如何解决这个问题?(I keep getting java.util.concurrentmodificationexception.. How to fix this?)
    问题 我一直在研究这段代码。 这是我想要发生的伪代码: a. 检查部分(这是一个列表)大小是否为 0。 b.如果部分大小为0,则通过调用sections.add(newSection)自动将学生注册到该部分 c.else 如果部分大小不为零,请检查是否与计划冲突 d.如果没有冲突,则通过调用sections.add(newSection)将学生注册到该部分 e.else 什么都不做 Java 不断向我抛出“java.util.concurrentmodificationexception”错误。 我知道,我不应该在遍历列表时改变 ArrayList 的大小,因为它会修改迭代器。 有没有另一种方法来解决这个问题? :D 非常感谢。 非常感谢您的帮助。 :) public String enrollsTo(Section newSection){ StringBuffer result = new StringBuffer(); String resultNegative = "Failed to enroll in this section."; String resultPositive = "Successfully enrolled in section: " + newSection.getSectionName() + "."; int
  • java开发:java集合(七):迭代器的fail-fast(快速失败)机制
    Inteator 是顶层的迭代器接口,定义了迭代器共同的方法。hasNext()判断集合是否含有下一个元素,next()获取下一个元素。每个具体的集合类中都有一个迭代器内部类实现于Inteator接口,用来历遍集合。 为了方便获取迭代器对象,java还提供了Iterable接口,使用iterator()方法来获取集合的迭代器。Collection 继承于Iterable接口,而所有具体的集合类都实现了Collection 。因此在我们使用集合时只需要调用集合对象的iterator()方法便可以得到迭代器对象。 /** 迭代器顶层接口 **/ public interface Inteator { public abstract boolean hasNext(); public abstract Object next(); } /** 获取迭代器对象的顶层接口 **/ public interface Iterable { Iterator iterator(); } /** Collection接口 继承Iterable **/ public interface Collection extends Iterable { Iterator iterator(); } public interface List extends Collection { Iterator
  • 如何创建同步数组列表(how to create Synchronized arraylist)
    问题 我已经创建了这样的同步arrayList import java.text.SimpleDateFormat; import java.util.*; class HelloThread { int i=1; List arrayList; public void go() { arrayList=Collections.synchronizedList(new ArrayList()); Thread thread1=new Thread(new Runnable() { public void run() { while(i<=10) { arrayList.add(i); i++; } } }); thread1.start(); Thread thred2=new Thread(new Runnable() { public void run() { while(true) { Iterator it=arrayList.iterator(); while(it.hasNext()) { System.out.println(it.next()); } } } }); thred2.start(); } } public class test { public static void main(String[] args) { HelloThread hello
  • 添加到 List 时抛出 java.util.ConcurrentModificationException(java.util.ConcurrentModificationException thrown when adding to List)
    问题 当我运行它时,尽管我使用了iterator.remove();但我还是得到了java.util.ConcurrentModificationException iterator.remove(); 显然是我在循环中添加了数字 6。 发生这种情况是否因为迭代器“不知道”它在那里并且无论如何要修复它? public static void main(String args[]){ List<String> list = new ArrayList<>(); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); for(Iterator<String> it = list.iterator();it.hasNext();){ String value = it.next(); if(value.equals("4")) { it.remove(); list.add("6"); } System.out.println("List Value:"+value); } } 回答1 调用String value = it.next();时抛出 ConcurrentModificationException String value = it.next(); . 但真正的罪魁祸首是list
  • java.util.ConcurrentModificationException异常解决和分析
    一、异常再现及解决方案 在遍历ArrayList并对其进行删除操作时,跳出java.util.ConcurrentModificationException异常,先上错误代码: @Test public void test1() { List<String> list = new ArrayList<String>(); list.add("小白"); list.add("小紫"); list.add("小红"); list.add("小绿"); list.add("小兰"); for (String str : list) { if(str.equals("小紫")){ list.remove(str); } } } @Test public void test2() { List<String> list = new ArrayList<String>(); list.add("小白"); list.add("小紫"); list.add("小红"); list.add("小绿"); list.add("小兰"); Iterator<String> it = list.iterator(); while(it.hasNext()){ String x = it.next(); if(x.equals("小紫")){ list.remove(x); } } }
  • Java,你告诉我 fail-fast 是什么鬼?
    1、前言 说起来真特么惭愧:十年 IT 老兵,Java 菜鸟一枚。今天我才了解到 Java 还有 fail-fast 一说。不得不感慨啊,学习真的是没有止境。只要肯学,就会有巨多巨多别人眼中的“旧”知识涌现出来,并且在我这全是新的。 能怎么办呢?除了羞愧,就只能赶紧全身心地投入学习,把这些知识掌握。 为了镇楼,必须搬一段英文来解释一下 fail-fast。 In systems design, a fail-fast system is one which immediately reports at its interface any condition that is likely to indicate a failure. Fail-fast systems are usually designed to stop normal operation rather than attempt to continue a possibly flawed process. Such designs often check the system’s state at several points in an operation, so any failures can be detected early. The responsibility of a fail-fast
  • 并发修改异常(Concurrent Modification Exception)
    问题 我目前正在处理多线程应用程序,偶尔会收到并发的修改异常(平均每小时大约一两次,但似乎是随机的时间间隔)。 错误的类本质上是地图的包装器-扩展了LinkedHashMap (将accessOrder设置为true)。 该类有一些方法: synchronized set(SomeKey key, SomeValue val) set方法将一个键/值对添加到内部映射,并受到synced关键字的保护。 synchronized get(SomeKey key) get方法基于输入键返回值。 rebuild() 有时会一次重建一次内部地图(每2分钟一次,间隔与异常不符)。 重建方法本质上是根据其键值重建值。 由于rebuild()相当昂贵,因此我没有在该方法上放置synced关键字。 相反,我在做: public void rebuild(){ /* initialization stuff */ List<SomeKey> keysCopy = new ArrayList<SomeKey>(); synchronized (this) { keysCopy.addAll(internalMap.keySet()); } /* do stuff with keysCopy, update a temporary map */ synchronized (this) {