代码之家  ›  专栏  ›  技术社区  ›  Borș Nicolae

可调用类中的并发修改异常

  •  0
  • Borș Nicolae  · 技术社区  · 8 年前

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

            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;
        }
    
    }
    

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

    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;
        }
    

    所以,当我试图执行它时,我得到了java。util。此行的ConcurrentModificationException for (Instance instance : instances) { 有人能告诉我为什么会这样吗?

    1 回复  |  直到 8 年前
        1
  •  2
  •   Andy Turner    8 年前
    public MyCallable (List<Instance> instances) {
        this.instances = Collections.synchronizedList(instances);
    }
    

    使用 synchronizedList 这样做对你没什么帮助。

    只有将列表包装在 同步列表 创建时(例如。 Collections.synchronizedList(new ArrayList<>()) 。否则,基础列表可以直接访问,因此可以以非同步方式访问。

    此外, 同步列表 仅在单个方法调用的持续时间内进行同步,而不是在迭代过程中的整个时间内进行同步。

    这里最简单的修复方法是复制列表 在构造函数中 :

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

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

    这与在 call 方法,因为复制是在代码的一个单线程部分中完成的:在复制该副本时,其他线程不能修改它,所以您不会得到 ConcurrentModificationException (您 可以 在单线程代码中获取CME,但不要使用此复制构造函数)。在中进行复制 呼叫 方法表示以与 for 您已经拥有的循环。