【问题标题】:concurrent modification exception happens at an expected time并发修改异常发生在预期时间
【发布时间】:2021-11-28 22:29:06
【问题描述】:

有一个列表同时被两个线程排序和迭代。正如预期的那样,它导致ConcurrentModificationException。目前尚不清楚错误发生的时间。

import java.util.stream.*;
import java.util.*;

public class Confusionist {
    static List<Integer> numbers;
    public static void main(String args[]) {
      numbers = IntStream.generate(() -> java.util.concurrent.ThreadLocalRandom.current().nextInt()).limit(100).boxed().collect(Collectors.toList());
      new Thread(new Accessor()).start();
      new Thread(new Accessor()).start();
    }
    
    static class Accessor implements Runnable{
        public void run(){
            String threadName = Thread.currentThread().getName();
            char threadDenoter = threadName.charAt(threadName.length()-1);
            System.out.printf("Thread: %s, Going to sort\n", threadName);
            Collections.sort(numbers, Integer::compareTo);
            Iterator<Integer> iterator = numbers.iterator();
            System.out.printf("Thread: %s, Going to iterate\n", threadName);
            while(iterator.hasNext()){
                iterator.next();
                System.out.printf("%c", threadDenoter);
            }
        }
    }
}

输出:(出现几次)

Thread: Thread-0, Going to sort
Thread: Thread-1, Going to sort
Thread: Thread-0, Going to iterate
0000000000000000000000000000000000000000000000000000000000000000000000000000000Exception in thread "Thread-0" java.util.ConcurrentModificationException
    at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:1013)
    at java.base/java.util.ArrayList$Itr.next(ArrayList.java:967)
    at HelloCodiva$Accessor.run(HelloCodiva.java:21)
    at java.base/java.lang.Thread.run(Thread.java:831)
000000Thread: Thread-1, Going to iterate
1111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111
Completed with exit code: 0

两个线程都对同一个列表进行了排序,它们已经获得了一个迭代器并且它们已经开始迭代。 (打印 0 和 1)。

当一个线程完成迭代时(这里线程 1 完成迭代,它已经打印了 100 个 1),另一个迭代失败。

  1. 为什么第一个线程完成后另一个线程的迭代失败?
  2. 当两个线程分别完成排序和获取迭代器时,底层迭代器不会改变。为什么这会导致现阶段出现异常?

【问题讨论】:

    标签: java multithreading iterator


    【解决方案1】:

    当两个线程分别完成排序并获得迭代器时,底层迭代器不会改变。为什么会导致现阶段出现异常?

    由于列表已经排序并且第二次排序不会使迭代器无效的假设是不正确的。如果thread-0初始化迭代器后thread-1对list进行排序,则thread-0获取的迭代器将失效并在访问时抛出ConcurrentModificationException。

    我可以想到一个操作序列来解释消息/错误。假设线程 0 对列表进行了排序,创建了一个迭代器并打印了 80 个奇数,然后发生了上下文切换。 Thread-1 将使用数组并打印 100 次。现在,当 thread-0 被重新调度时,它的迭代器将不再有效并抛出异常。

    为了自己测试,我编写了这个简单的代码,它抛出了 ConcurrentModificationException。

    List<Integer> list =
            IntStream.generate(() -> java.util.concurrent.ThreadLocalRandom.current().nextInt(10))
                .limit(10)
                .boxed()
                .collect(Collectors.toList());
        Collections.sort(list, Integer::compareTo);
        Iterator<Integer> itr = list.iterator();
        Collections.sort(list, Integer::compareTo);
    
        while (itr.hasNext()) {
          System.out.println(itr.next());
        }
    

    有趣的部分是这仅适用于列表 Collectors.toList() 返回的类型。如果只使用 ArrayList,我看不到相同的行为。

    【讨论】:

      猜你喜欢
      • 2016-11-19
      • 1970-01-01
      • 1970-01-01
      • 2013-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多