还有另一个ConcurrentModificationException问题

时间:2011-06-09 13:48:32

标签: java multithreading

我目前正在尝试学习如何正确处理对Collections的多线程访问,因此我编写了以下Java应用程序。

正如你所看到的,我创建了一个同步的ArrayList,我尝试从一个Thread中访问一次,一次没有。

我使用for循环迭代ArrayList。为了防止同时对List进行多次访问,我将循环包装到了一个synchronized块中。

public class ThreadTest {

    Collection<Integer> data    = Collections.synchronizedList(new ArrayList<Integer>());
    final int           MAX     = 999;

    /**
     * Default constructor
     */
    public ThreadTest() {
        initData();
        startThread();
        startCollectionWork();
    }

    private int getRandom() {
        Random randomGenerator = new Random();
        return randomGenerator.nextInt(100);
    }

    private void initData() {
        for (int i = 0; i < MAX; i++) {
            data.add(getRandom());
        }
    }

    private void startCollectionWork() {
        System.out.println("\nStarting to work on data outside of thread");
        synchronized (data) {
            System.out.println("\nEntered synchronized block outside of thread");
            for (int value : data) { // ConcurrentModificationException here!
                if (value % 5 == 1) {
                    System.out.println(value);
                    data.remove(value);
                    data.add(value + 1);
                } else {
                    System.out.println("value % 5 = " + value % 5);
                }
            }
        }
        System.out.println("Done working on data outside of thread");
    }

    private void startThread() {
        Thread thread = new Thread() {
            @Override
            public void run() {
                System.out.println("\nStarting to work on data in a new thread");
                synchronized (data) {
                    System.out.println("\nEntered synchronized block in thread");
                    for (int value : data) { // ConcurrentModificationException
                        if (value % 5 == 1) {
                            System.out.println(value);
                            data.remove(value);
                            data.add(value + 1);
                        } else {
                            System.out.println("value % 5 = " + value % 5);
                        }
                    }
                }
                System.out.println("Done working on data in a new thread");
            }
        };
        thread.start();
    }
}

但每次输入一个for循环时,我都会得到一个ConcurrentModificationException。这是我的控制台输出(每次新运行都会改变):

Starting to work on data outside of thread

Entered synchronized block outside of thread
51

Starting to work on data in a new thread

Entered synchronized block in thread
value % 5 = 2
value % 5 = 2
value % 5 = 4
value % 5 = 3
value % 5 = 2
value % 5 = 2
value % 5 = 0
21
Exception in thread "main" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at ThreadTest.startCollectionWork(ThreadTest.java:50)
    at ThreadTest.<init>(ThreadTest.java:32)
    at MultiThreadingTest.main(MultiThreadingTest.java:18)
Exception in thread "Thread-1" java.util.ConcurrentModificationException
    at java.util.AbstractList$Itr.checkForComodification(AbstractList.java:372)
    at java.util.AbstractList$Itr.next(AbstractList.java:343)
    at ThreadTest$1.run(ThreadTest.java:70)

怎么了?

Ps:请不要只发布多线程操作方法的链接,因为我已经阅读了足够的内容。我很好奇为什么我的应用程序没有像我想要的那样运行。


更新:我用明确的Iterator和while循环替换了for(x:y)语法。问题仍然存在..

synchronized(data){
        Iterator<Integer> i = data.iterator();
        while (i.hasNext()) {
        int value = i.next(); // ConcurrentModificationException here!
        if (value % 5 == 1) {
            System.out.println(value);
            i.remove();
            data.add(value + 1);
        } else {
            System.out.println("value % 5 = " + value % 5);
        }
    }
}

7 个答案:

答案 0 :(得分:3)

一旦迭代了一个集合,你就会在迭代的代码块和那个时刻存在的集合之间达成一个契约。合同基本上表明你将按照迭代的顺序获得集合中的每个项目。

问题在于,如果在迭代时修改Collection,则无法维护该合约。集合中的删除将从集合中删除元素,并且可能需要该元素出现在初始迭代中以满足合同。如果在集合中存在元素之前开始的迭代可能检测到元素,则集合中的插入同样会出现问题。

虽然使用多个线程更容易破解此合约,但您可以通过单个线程(如果您选择这样做)破解合同。

通常如何实现这个集合包含一个“版本号”,并且在迭代器抓取集合中的“next”元素之前,它会检查集合的修订版号是否仍然与它时相同迭代器开始了。这只是实现它的一种方式,还有其他方法。

因此,如果您想迭代可能想要更改的内容,则适当的技术是制作集合的副本并迭代该副本。这样您就可以修改原始集合,但不会改变您计划处理的项目的数量,位置和存在。是的,还有其他技术,但从概念上讲,它们都属于“保护您正在迭代的副本,同时更改迭代器无法访问的其他内容”。

答案 1 :(得分:2)

增强的for循环

for (int value : data) { 

使用Java Iterator。迭代器是快速失败的,因此如果在Iterator处于活动状态时底层Collection被修改(即通过删除元素),则会得到异常。您的代码会导致对基础Collection的更改:

data.remove(value);
data.add(value + 1);

将代码更改为显式使用java.util.Iterator并使用其remove方法。如果您需要在迭代时向集合中添加元素,则可能需要查看来自java.util.concurrent包的合适数据结构,例如BlockingQueue,您可以在其中调用take方法,该方法将阻止,直到有数据存在;但是可以通过offer方法添加新对象(非常简单的概述 - Google了解更多)

答案 2 :(得分:2)

出现ConcurrentModificationException,因为你在迭代它时修改列表......在这种情况下它与多个线程无关......

for (int value : data) { // ConcurrentModificationException here!
                if (value % 5 == 1) {
                    System.out.println(value);
                    data.remove(value); // you cannot do this
                    data.add(value + 1); // or that
                } else {
                    System.out.println("value % 5 = " + value % 5);
                }

答案 3 :(得分:1)

如果要遍历集合,则只允许通过迭代器从列表中删除项目,因此只能使用一个线程获得ConcurrentModificationException。

更新后的问题更新回复: 在迭代时,不允许向列表中添加元素。

答案 4 :(得分:1)

问题不在于多线程,如果安全的话。但这只是因为你在迭代时修改了集合。

答案 5 :(得分:0)

正如已经指出的那样,你在迭代它时修改了Collection,这会导致你的问题。

数据更改为List,然后使用常规for循环来跳过它。然后你将不再有迭代器来处理,从而消除你的问题。

List<Integer> data =...

for (int i=0; i<data.size(); i++) {
        int value = data.get(i);

        if (value % 5 == 1) {
            System.out.println(value);
            i.remove();
            data.add(value + 1);
        } else {
            System.out.println("value % 5 = " + value % 5);
        }
}

答案 6 :(得分:0)

通过在完成迭代的集合周围使用synchronized块,不会消除ConcurrentModificationException。以下一系列步骤发生异常:

  1. 从集合中获取迭代器(通过调用其迭代器方法或for循环结构)。
  2. 开始迭代(通过调用next()或for for循环)
  3. 修改集合(通过迭代器的方法之外的任何方式)(在同一个线程或不同的线程中:这是您的代码中发生的事情)。请注意,此修改可能以线程安全的方式发生,即顺序或一个接一个 - 无关紧要 - 它仍然会导致CME)
  4. 使用之前获得的相同迭代器(在步骤3中修改之前)继续迭代
  5. 为了避免异常,必须确保在任一线程中启动for循环后不修改集合,直到循环结束。