java.util.ConcurrentModificationException:多线程时修改意外的列表?

时间:2013-07-23 04:31:38

标签: java multithreading list concurrency

我正在使用多线程来批量处理字符串列表,但是当Runnable任务迭代List以处理每个字符串时,我收到此错误。

例如,代码大致遵循以下结构:

public class RunnableTask implements Runnable {

private List<String> batch;

    RunnableTask(List<String> batch){
        this.batch = batch;
    }


    @Override
    public void run() {

        for(String record : batch){

            entry = record.split(",");
            m = regex.matcher(entry[7]);

            if (m.find() && m.group(3) != null){
                currentKey = m.group(3).trim();
                currentValue = Integer.parseInt(entry[4]);

                if ( resultMap.get(currentKey) == null ){
                    resultMap.put(currentKey, currentValue);
                } else {
                    resultMap.put(currentKey, resultMap.get(currentKey) + currentValue);
            }   
        }

    }
} 
}       

通过这些批处理进行处理的线程永远不会修改“批处理”,并且在for循环内不会批量处理批处理。我知道这个异常ConcurrentModificationException是由于在迭代期间修改了List,但据我所知,没有发生。有什么我想念的吗?

感谢任何帮助,

三江源!

UPDATE1:似乎实例变量不是线程安全的。我试图使用CopyOnWriteArrayList代替ArrayList但是我收到了不一致的结果 - 表明在以某种方式修改列表并且不是每个元素都被处理之前,完整的迭代没有完成。

UPDATE2:使用sychronized和/或reentrantlock锁定循环仍然会产生相同的异常。

我需要一种方法将列表传递给Runnable任务,并在没有新线程的情况下迭代这些列表,从而导致该列表出现并发问题。

7 个答案:

答案 0 :(得分:2)

  

我理解这个异常ConcurrentModificationException是由于在迭代过程中修改了List,但据我所知,没有发生

好的,请考虑创建新线程时发生的情况,将引用传递给RunnableTask实例,使用不同的列表初始化为构造函数参数?您刚刚更改了列表引用以指向不同的列表。并且考虑一下,当run()方法中的不同线程在任何时候改变list时会发生什么。这将在某个时间点抛出ConcurrentModificationException

实例变量不是线程安全

答案 1 :(得分:0)

在您的代码中尝试此操作:

public void run() {
    for(String record : new ArrayList(batch)){
        //do processing with record
    }
}

处理列表的所有线程都有一个问题(在进程中修改的列表是什么?)但很难用你提供的代码判断

答案 2 :(得分:0)

问题是由于多个线程同时修改源List结构。我建议你应该将源列表分配给新的子列表(根据大小)并将该列表传递给线程。

说你的来源List有100个元素。并且您正在运行5个并发线程。

int index = 0;
List<TObject> tempList = new ArrayList<>();
for(TObject obj:srcList){
    if(i==(srcList.size()/numberOfthread)){
      RunnableTask task = new RunnableTask(tempList);
      tempList = new ArrayList<>();
    }else 
       tempList.add(obj);
}

在这种情况下,您的原始列表不会被修改。

答案 3 :(得分:0)

您需要在访问其元素之前锁定列表。因为List不是线程安全的。试试这个

   public void run() {
     synchronizd(batch){
         for(String record : batch){//do processing with record}
     }
   }

答案 4 :(得分:0)

是的,你得到了ConcurrentModificationException,因为你的List在迭代期间被修改了。如果性能不是关键问题,我建议使用同步。 公共类RunnableTask实现Runnable {

private List<String> batch = new ArrayList<String>();

RunnableTask(List<String> batch){
    this.batch = batch;
}


public void run() {
    synchronized (batch) {
        for(String record : batch){//do processing with record}
        }
    }

}
}

甚至更好地使用ReentrantLock

答案 5 :(得分:0)

您的后续操作表明您尝试多次重复使用相同的List。您的调用者必须为每个Runnable创建一个新列表。

答案 6 :(得分:0)

显然,其他人正在更改列表的内容,这与您提到的代码不相符。 ( 如果您确定ConcurrentModificationException正在抱怨batch列表,而不是resultMap,并且您实际上是在RunnableTask中显示所有代码

尝试搜索您的代码,对于正在更新列表内容的地方,请检查是否可以与RunnableTask同时进行。

只需在RunnableTask中进行同步就没有用,你需要同步对列表的所有访问,这显然发生在其他地方。

如果性能对您来说是一个问题,那么您无法在batch列表上同步(禁止多个RunnableTask同时执行),请考虑使用ReaderWriterLock:RunnableTask获取读锁定,而列表更新逻辑获取写锁定。