【问题标题】:java.util.ConcurrentModificationException: Unexpected List modification while multithreading?java.util.ConcurrentModificationException:多线程时意外的列表修改?
【发布时间】:2013-07-23 04:31:37
【问题描述】:

我正在使用多线程来批量处理字符串列表,但是当 Runnable 任务遍历列表以处理每个字符串时出现此错误。

例如代码大致遵循这样的结构:

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 是由于在迭代期间修改了列表,但据我所知,这并没有发生。我有什么遗漏吗?

感谢您的帮助,

谢谢!

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

UPDATE2:使用同步锁和/或重入锁锁定循环仍然会出现相同的异常。

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

【问题讨论】:

  • 鉴于您没有在 foreach 循环中显示代码,因此没有人知道。另外,您的列表是否在可运行文件的外部修改?
  • 我敢打赌,要么调用者,要么你的循环正在修改批次。
  • 循环内的处理只使用记录,不参考批处理。
  • 使用列表迭代器,而不是直接遍历列表并修改它。
  • 您的更新证明调用者或某人正在修改列表。 p.s.尝试使您的实例 var batch final。

标签: java multithreading list concurrency


【解决方案1】:

我知道这个异常 ConcurrentModificationException 是由于在迭代期间修改了 List 但据我所知没有发生

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

实例变量不是线程安全的

【讨论】:

  • 我认为如果每个新线程都是一个单独的实例,它是线程安全的。你能建议一个线程安全的修改吗?
  • 我错过了一些东西。他的 RunnableTask 引用了 List。施工后不会改变。没有“列表引用”,有 n 个单独的引用。据我了解他的代码。注意:如果将批次声明为最终版本会更好。
  • @howlerdingdong。你可以看看java.util.concurrent.CopyOnWriteArrayList
  • @user949300。您可以创建多个线程传递RunnableTask 的相同引用。尝试这样做。
  • 假设 run() 不修改批处理它应该可以工作。虽然这么晚了,但我可能会错过一些东西。我敢打赌,他在 run() 中的循环间接地改变了一些东西。
【解决方案2】:

在你的代码中试试这个:

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

处理列表的所有线程都存在某种问题(在此过程中是否修改了列表?)但很难用您提供的代码来判断

【讨论】:

    【解决方案3】:

    问题是由于多个线程同时修改源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);
    }
    

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

    【讨论】:

    • 更改批量大小对并发性有何影响?
    • 不,你错过了重点,它正在创建一个全新的列表对象
    【解决方案4】:

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

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

    【讨论】:

    • 在 for 循环中使用同步仍然给我异常,因为当新线程实例化在类级别修改批处理时,批处理的更改正在发生。
    【解决方案5】:

    是的,您得到 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

    【讨论】:

    • 不幸的是,性能是一个问题,我正在处理数十万个批次。
    • 然后使用 ReentrantLock。它在读取时不会锁定(您显然在处理中所做的事情)它会进行易失性读取。它只会锁定写入。
    • reentrantlock 和 synchronized 实际上仍然给我同样的异常,再次考虑到修改不是来自 for 循环,而是来自每个新线程的批处理重新实例化。
    【解决方案6】:

    您的跟进表明您正尝试多次重复使用同一个列表。您的调用者必须为每个 Runnable 创建一个新列表。

    【讨论】:

      【解决方案7】:

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

      尝试在您的代码中搜索正在更新列表内容的位置,检查是否可以与您的RunnableTask 同时进行。

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2010-12-21
        • 1970-01-01
        • 1970-01-01
        • 2013-06-04
        • 1970-01-01
        • 2019-11-14
        • 1970-01-01
        相关资源
        最近更新 更多