ConcurrentModificationException当多个线程访问同一个Collection时

时间:2015-11-14 05:11:46

标签: java

我有2个Main类的内部线程类。有时,当一个添加新元素而另一个元素被删除时,它会导致ConcurrentModificationException。我想我不知道如何同步它们。

NSURLCache

4 个答案:

答案 0 :(得分:2)

ConcurrentModificationException是由ThreadA中的set.add(obj)引起的,而迭代在ThreadB中进行(而不是在循环期间由set.remove())。

需要同步线程以避免这种情况。

使用某些对象上的内部锁来同步线程。您使用' synchronized'来声明这一点。关键字:

// entire method synchronized on 'this'
synchronized SomeValue foo();

// block synchronized on obj:
synchronized( obj ) {
    // stuff.
}

根据您需要同步的内容,细节会有很大差异。对于集合,通常可以安全地隔离特定操作,如add()或remove(),但如果需要迭代集合中的元素,则需要同步整个块,如果您进行迭代使用常规集合实现:

synchronized( set ) {
    for (Iterator<MyObject> i = set.iterator(); i.hasNext();) {
        ...
    }
}

但是,通常可以根据集合的性质实现更有效的同步,并且在手动同步时很容易出错。对于大多数情况,通常最好只使用java.util.concurrent中的一个集合实现,它实现了线程安全的迭代器,并且不会从其他线程的操作中抛出ConcurrentModificationException。

由于某种原因,没有Set的ConcurrentHashSet实现,但是可以使用newSetFromMap获取一个实例:

HashSet<MyObject> set = Collections.newSetFromMap( new ConcurrentHashMap<MyObject,Object>() );

答案 1 :(得分:1)

  1. 使用集合副本删除项目,迭代集合时无法删除项目。

  2. 要同步,请使用Lock()集合。

    Lock myLock= new Lock();
    myLock.lock();
    set.add(item);
    myLock.unlock();
    
    myLock.lock();
    ...while loop and modification..
    myLock.unlock();
    

答案 2 :(得分:1)

最简单的解决方案是将读取代码与写入代码隔离开来。您可以通过使用synchronized(set)块包围修改来实现此目的。对于第一个呼叫,我们必须围绕添加呼叫进行同步:

run(running){
...
    synchronized(set) {
        set.add(obj);
    }
...
}

对于第二次调用,我们需要在整个迭代期间进行同步以避免并发修改。 i.remove()在单线程情况下是正确的,但正如您所发现的,它在多个线程中不起作用。

synchronized(set) {
    for (Iterator<MyObject> i = set.iterator(); i.hasNext();) {
        MyObject obj= i.next();
        if (!obj.isSmt()) {
            i.remove();
           ...
        }
    }
}

synchronized(set)是对象set的锁定。只有一个线程能够在给定时间输入任一个同步块,防止在线程迭代时将项目添加到集合中。

答案 3 :(得分:0)

在迭代元素时,您无法修改元素列表。这将导致ConcurrentModificationException。如果要删除元素,请将其存储在临时列表中,然后在迭代完成后删除列表。