多个线程访问同一集合时出现ConcurrentModificationException异常

5

我有2个位于Main类中的内部线程类。有时当一个线程添加新元素,另一个线程却在删除元素时会导致ConcurrentModificationException异常。我认为我不知道如何使它们同步。

Class Main{
HashSet<MyObject> set;   
Thread A{
       run(running){
          ...
          set.add(obj);
          ...
       }
    }

Thread B{
     run(){
      while (running) {
                for (Iterator<MyObject> i = set.iterator(); i.hasNext();) {
                    MyObject obj= i.next();
                    if (!obj.isSmt()) {
                        i.remove();
                       ...
                    }
                }
            }
     }
}

}
3个回答

6

最简单的解决方案是将读取代码与写入代码分离。您可以通过在修改周围使用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加锁。在同一时间内,只有一个线程能够进入锁定代码块, 防止在迭代期间向集合中添加元素。


1
我相信语法是synchronized(set) { ... } - Andreas
作为一个新手Java程序员,我真希望早点看到这个答案。我一直在处理线程之间的并发问题。这个方法看起来如此简单、干净和快速。谢谢! - Fluffeh

5

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。
由于某种原因,没有ConcurrentHashSet实现Set,但是可以通过使用newSetFromMap来获得一个实例。
HashSet<MyObject> set = Collections.newSetFromMap( new ConcurrentHashMap<MyObject,Object>() );

1
  1. Use a copy of collection to remove item, you cannot remove item while iterating over collection.

  2. To synchronize use the Lock() over collection.

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接