防止并发修改异常的最佳方法

5

以下是一些伪代码。

public class MyObject
{   
    private List<Object> someStuff;
    private Timer timer;

    public MyObject()
    {
        someStuff = new ArrayList<Object>();

        timer = new Timer(new TimerTask(){

            public void run()
            {
                for(Object o : someStuff)
                {
                    //do some more stuff involving add and removes possibly
                }
            }
        }, 0, 60*1000);
    }

    public List<Object> getSomeStuff()
    {
        return this.someStuff;
    }
}

问题在于上面代码中未列出的其他对象调用getSomeStuff()方法仅获取列表进行只读操作时,定时器线程会发生concurrentmodificationexception异常。我尝试将getSomeStuff方法设为同步方法,并在定时器线程中使用同步块,但仍然会出现错误。最简单的方法是如何停止对列表的并发访问?

2个回答

14

你可以使用java.util.concurrent.CopyOnWriteArrayList,或在线程遍历列表之前复制列表(或使用Collection.toArray方法获取数组)。

除此之外,在foreach循环中删除元素会破坏迭代器,因此这不是在此情况下处理列表的有效方法。

但你可以采用以下方式:

for (Iterator<SomeClass> i = list.iterator(); i.hasNext();) {
    SomeClass next = i.next();
    if (need_to_remove){
       i.remove(i);                
    }
}
或者
for (int i = list.size() - 1; i >= 0; i--){            
    if (need_to_remove) {
        list.remove(i);                
    }
}

需要注意的是,如果您的代码从不同的线程访问列表并且该列表被修改了,您需要对其进行同步。例如:

    private final ReadWriteLock lock = new ReentrantReadWriteLock();


    final Lock w = lock.writeLock();
    w.lock();
    try {
        // modifications of the list
    } finally {
        w.unlock();
    }

      .................................

    final Lock r = lock.readLock();
    r.lock();
    try {
        // read-only operations on the list
        // e.g. copy it to an array
    } finally {
        r.unlock();
    }
    // and iterate outside the lock 

但请注意,锁内操作应尽可能短。


但是,在线程中完成的工作(添加和删除)必须实际上存在于列表对象中。我只是不希望其他线程访问该列表以进行读取(foreach循环),从而导致JVM出现问题。 - thatidiotguy
我猜我只是觉得使用Java的synchronize关键字做些什么是可能的,但你说这不是这种情况吗?那么这些Lock和ReadWriteLock在哪些包中?另外,我只需要在计时器的run方法执行时锁定列表本身,这是你推荐的方法吗? - thatidiotguy
正确。您迭代/删除的方式会在单线程环境下出现问题。Java.util.concurrent包中的锁类。您锁定的不是列表,而是对其的访问。即列表操作在锁内执行(尽管读写操作之间存在差异-因此读取器不会相互阻塞)。 - Eugene Retunsky
我在代码中并没有看到任何提到列表的内容,你只是在构建这个锁对象,但从未提到列表。它是否阻止对任何对象的写入? - thatidiotguy
代码块中有注释解释应该放什么内容。 - Eugene Retunsky

4
你应该在getSomeStuff()中复制列表。像这样发布对私有字段的引用会使其实际上变为公共的,所以无论如何都不是你想做的事情。
此外,考虑返回一个副本作为ImmutableList或至少作为不可修改的列表

我现在正在返回Collections.synchronizedList(但它不起作用)。接收该列表的对象应该能够迭代以进行读取,但仅限于此。这些解决方案中的一个是否可以解决concurrentmodificationexception问题?不过,关于对象安全性的观点已经被采纳了。 - thatidiotguy

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