通过制作浅表副本来避免List上的ConcurrentModificationException

时间:2011-08-24 02:49:16

标签: java list shallow-copy concurrentmodification

我有一个如下课程:

class Test
{
    private LinkedList<Person> persons = new LinkedList<Person>;

    public synchronized void remove(Person person)
    {
        persons.remove(person);
    }

    public List<Person> getAllPersons()
    {
        // Clients may iterate over the copy returned and modify the structure.
        return new ArrayList<Person>(persons);
    }
}

persons可以同时修改:一个通过remove()通过一个线程,两个通过getAllPersons()返回的浅复制实例。

我已经在多线程环境中测试了上述场景,看看我是否可以在调用ConcurrentModificationException时返回浅拷贝来避免getAllPersons()。它似乎工作。我从未遇到过ConcurrentModificationException

在这种情况下,为什么只制作persons的浅层副本避免ConcurrentModificationException

2 个答案:

答案 0 :(得分:6)

当集合以使打开的迭代器无效的方式更改时,将引发ConcurrentModificationException。当多个线程访问非线程安全的集合时,通常会发生这种情况(尽管这不是唯一原因)

您的代码中仍然存在一个小错误 - 为了安全地访问本身不是线程安全的成员,您应该在getAllPersons方法上synchronize

假设已修复 - 因为您正在返回副本,其他调用者无法修改集合本身(每个调用者都获得他们自己的副本)。这意味着你永远不会得到ConcurrentModificationException。

请注意,此不会保护您免受Person类的线程安全问题,只保护集合本身。如果Person是不可变的,那么你应该没问题。

在这种情况下,更好的解决方案是直接使用实现类似语义的CopyOnWriteArrayList,但只有在实际写入列表时才复制 - 而不是每次从中读取时都是。

答案 1 :(得分:1)

那是因为你要返回List的副本而不是列表本身。 remove()是修改实际列表的唯一方法,可由多个线程访问。调用getAllPersons()方法的线程无论如何都会获得一个新列表,所以如果它们修改了这个列表,它就不会改变原始列表。 因此,您的集合不会被线程同时修改,因此您没有收到ConcurrentModificationException。