CopyOnWriteArrayList迭代器与多线程不一致?

时间:2013-07-23 07:05:56

标签: java multithreading concurrency iteration

我正在使用ExecutorService在CopyOnWrite ArrayLists中发送批量的字符串并行处理,其中处理这些列表的Runnable任务需要遍历列表并对每个字符串进行处理。

在使用常规ArrayLists遇到并发问题后,我尝试使用CopyOnWriteArrayLists,因为它们是线程安全的,但是我的结果现在不一致了。也就是说,我在程序的每次运行中得到不同的结果,这表明在每个Runnable taks可以完全迭代之前,arraylist的内容会以某种方式改变。

public static class BatchRunnable implements Runnable {

    private CopyOnWriteArrayList<String> batch;

    BatchRunnable(CopyOnWriteArrayList<String> batch){
        this.batch = batch;
    }

    @Override
    public void run(){
        //iterate over batch and work with String elements
        //make no modifications to batch
    }
}
  • runnable任务对arraylist进行NO修改,它只遍历列表并使用列表中的String元素进行处理。

  • 更改CopyOnWriteArrayList的唯一位置是使用每个新的Runnable任务进行实例化。

当我传入单个字符串而不是批处理时,我得到了一致且正确的结果,但是当我开始在String ArrayLists中使用批处理时,我得到了不一致的结果,表明有些东西会影响CopyOnWriteArrayList批处理的并发性,尽管它是据说是线程安全的。

感谢任何帮助,谢谢!

编辑:这是我的批次构建的地方:

        Runnable worker = null;
        while((line = br.readLine()) != null) {
            recordBatch.add(line);
            if(recordBatch.size() == 100){
                worker = new BatchRunnable(recordBatch);
                executor.execute(worker);
                recordBatch.clear();
            }

        }           
        executor.shutdown();
        executor.awaitTermination(60,TimeUnit.SECONDS);  

4 个答案:

答案 0 :(得分:2)

查看您的while循环:

while((line = br.readLine()) != null) {
        recordBatch.add(line);
        if(recordBatch.size() == 100){
            worker = new BatchRunnable(recordBatch);
            executor.execute(worker);
            recordBatch.clear();
        }

    }  

您将所有list中的引用传递给同一BatchRunnable。因此,只要您在一个地方更改list,它就会反映在所有参考中。因此,一旦您使用recordBatch.clear()清除列表,列表对于所有引用都是空的,甚至是您在BatchRunnable中的列表。这就是你得到不一致结果的原因。

您应该在copy中传递recordBatchBatchRunnable列表:

worker = new BatchRunnable(new ArrayList<String>(recordBatch));

答案 1 :(得分:1)

您将批次传递给BatchRunnable后清除。

 worker = new BatchRunnable(recordBatch);
 executor.execute(worker);
 recordBatch.clear(); // You clear all the list

因此执行程序将处理列表中的任何内容但是如果到达clear()行(并且因为执行程序在不同的线程上运行,这可能发生在BatchRunnable完成之前),那么列表将为空(或包含下一批!)并且工作批将具有不一致的列表。

当您将列表传递给工作人员时,您传递的是引用而不是副本!因此,要么为每个批次复制批次或创建一个新批次:

 worker = new BatchRunnable(recordBatch);
 executor.execute(worker);
 recordBatch = new CopyOnWriteArrayList<String>();

答案 2 :(得分:0)

我猜你使用迭代器来浏览元素。迭代器提供构造迭代器时列表状态的快照。遍历迭代器时不需要同步。

因此,在您的情况下,您应该在构造函数中获取迭代器,或者复制CopyOnWriteArrayList

答案 3 :(得分:0)

如果您遇到简单问题ArrayList遇到并发问题,则表明在BatchRunnable迭代它时会对其进行修改。 用ArrayList替换CopyOnWriteArrayList只会隐藏并发问题。

在您的代码中,您在创建BatchRunnable时修改了列表clear()add()。当第一个runnable被提交时,它会开始处理列表,但您仍然会继续修改它。