Callable类中的并发修改异常

问题描述 投票:0回答:1

我正在尝试在较小的子列表中拆分对象列表,并在不同的线程上单独处理它们。所以我有以下代码:

        List<Instance> instances = xmlInstance.readInstancesFromXml();
        List<Future<List<Instance>>> futureList = new ArrayList<>();

        int nThreads = 4;

        ExecutorService executor = Executors.newFixedThreadPool(nThreads);

        final List<List<Instance>> instancesPerThread = split(instances, nThreads);

        for (List<Instance> instancesThread : instancesPerThread) {
            if (instancesThread.isEmpty()) {
                break;
            }

            Callable<List<Instance>> callable = new MyCallable(instancesThread);
            Future<List<Instance>> submit = executor.submit(callable);
            futureList.add(submit);
        }

        instances.clear();

        for (Future<List<Instance>> future : futureList) {
            try {
                final List<Instance> instancesFromFuture = future.get();
                instances.addAll(instancesFromFuture);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        executor.shutdown();

        try {
            executor.awaitTermination(Long.MAX_VALUE, TimeUnit.NANOSECONDS);
        } catch (InterruptedException ie) {
            ie.printStackTrace();
        }

和MyCallable类:

public class MyCallable implements Callable<List<Instance>> {

    private List<Instance> instances;

    public MyCallable (List<Instance> instances) {
        this.instances = Collections.synchronizedList(instances);
    }


    @Override
    public List<Instance> call() throws Exception {

        for (Instance instance : instances) {
            //process each object and changing some fields;
        }

        return instances;
    }

}

拆分方法(它在给定的列表数中拆分给定列表,也尝试在每个子列表上具有几乎相同的大小):

public static List<List<Instance>> split(List<Instance> list, int nrOfThreads) {
        List<List<Instance>> parts = new ArrayList<>();
        final int nrOfItems = list.size();
        int minItemsPerThread = nrOfItems / nrOfThreads;
        int maxItemsPerThread = minItemsPerThread + 1;
        int threadsWithMaxItems = nrOfItems - nrOfThreads * minItemsPerThread;
        int start = 0;
        for (int i = 0; i < nrOfThreads; i++) {
            int itemsCount = (i < threadsWithMaxItems ? maxItemsPerThread : minItemsPerThread);
            int end = start + itemsCount;
            parts.add(list.subList(start, end));
            start = end;
        }

        return parts;
    }

所以,当我试图执行它时,我在这行for (Instance instance : instances) {上得到了java.util.ConcurrentModificationException,有人可以提出任何想法,为什么会发生这种情况?

java concurrency future callable
1个回答
2
投票
public MyCallable (List<Instance> instances) {
    this.instances = Collections.synchronizedList(instances);
}

像这样使用synchronizedList并没有像你想象的那样帮助你。

只有在创建它时将列表包装在synchronizedList中才有用(例如Collections.synchronizedList(new ArrayList<>())。否则,底层列表可以直接访问,因此可以以不同步的方式访问。

此外,synchronizedList仅在单个方法调用的持续时间内同步,而不是在迭代它时的整个时间。

这里最简单的解决方法是在构造函数中获取列表的副本:

    this.instances = new ArrayList<>(instances);

然后,没有其他人可以访问该列表,因此他们在迭代时无法更改它。

这与在call方法中获取列表的副本不同,因为副本是在代码的单线程部分完成的:在您获取该副本时没有其他线程可以修改它,因此您将无法获得ConcurrentModificationException(您可以在单线程代码中获得CME,但不使用此复制构造函数)。在call方法中执行复制意味着列表被迭代,与您已经拥有的for循环完全相同。

© www.soinside.com 2019 - 2024. All rights reserved.