0

我正在使用多线程批量处理字符串列表,但是当 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:使用 sychronized 和/或 reentrantlock 锁定循环仍然会给出相同的异常。

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

4

7 回答 7

2

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

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

实例变量不是线程安全的

于 2013-07-23T04:34:27.590 回答
0

you need to lock the list before accessing its elements. because List is not thread safe. Try this

   public void run() {
     synchronizd(batch){
         for(String record : batch){//do processing with record}
     }
   }
于 2013-07-23T04:45:05.273 回答
0

yes you are getting ConcurrentModificationException because your List is getting modified during iteration. If performance is not a critical issue I suggest use synchronization. public class RunnableTask implements 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}
        }
    }

}
}

or even better use ReentrantLock.

于 2013-07-23T04:46:38.693 回答
0

Your followups indicate that you are trying to reuse the same List multiple times. Your caller must create a new List for each Runnable.

于 2013-07-24T00:09:55.593 回答
0

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

尝试在您的代码中搜索正在更新列表内容的位置,检查它是否可能与您的RunnableTask.

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

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

于 2013-07-24T02:49:05.853 回答
0

在你的代码中试试这个:

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

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

于 2013-07-23T04:33:29.627 回答
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);
}

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

于 2013-07-23T04:38:21.247 回答